Obecnie próbuję zaprojektować architekturę nowej gry mobilnej MMORPG dla mojej firmy. Ta gra jest podobna do Mafia Wars, iMobsters lub RISK. Podstawowym pomysłem jest przygotowanie armii do walki z przeciwnikami (użytkownicy online).
Chociaż wcześniej pracowałem nad wieloma aplikacjami mobilnymi, ale jest to dla mnie coś nowego. Po wielu zmaganiach opracowałem architekturę ilustrowaną za pomocą schematu wysokiego poziomu:
Zdecydowaliśmy się na model klient-serwer. Na serwerze będzie scentralizowana baza danych. Każdy klient będzie miał własną lokalną bazę danych, która pozostanie zsynchronizowana z serwerem. Ta baza danych działa jako pamięć podręczna do przechowywania rzeczy, które nie zmieniają się często, np. Mapy, produkty, zapasy itp.
Po wdrożeniu tego modelu nie jestem pewien, jak rozwiązać następujące problemy:
- Jaki byłby najlepszy sposób synchronizacji baz danych serwera i klienta?
- Czy zdarzenie powinno zostać zapisane w lokalnej bazie danych przed aktualizacją do serwera? Co jeśli aplikacja z jakiegoś powodu zostanie zakończona przed zapisaniem zmian w scentralizowanej bazie danych?
- Czy proste żądania HTTP będą służyć synchronizacji?
- Jak sprawdzić, którzy użytkownicy są obecnie zalogowani? (Jednym ze sposobów może być wysyłanie przez klienta żądania do serwera co x minut w celu powiadomienia, że jest ono aktywne. W przeciwnym razie należy uznać klienta za nieaktywny).
- Czy wystarczające są weryfikacje po stronie klienta? Jeśli nie, jak cofnąć akcję, jeśli serwer czegoś nie zweryfikuje?
Nie jestem pewien, czy jest to skuteczne rozwiązanie i jak się skaluje. Byłbym bardzo wdzięczny, gdyby ludzie, którzy już pracowali nad takimi aplikacjami, mogli podzielić się swoimi doświadczeniami, które mogą pomóc mi wymyślić coś lepszego. Z góry dziękuję.
Dodatkowe informacje:
Po stronie klienta zaimplementowano silnik gry C ++ o nazwie marmolada. Jest to wieloplatformowy silnik gry, co oznacza, że możesz uruchomić aplikację na wszystkich głównych systemach operacyjnych dla urządzeń mobilnych. Z pewnością możemy osiągnąć gwintowanie, co zostało również zilustrowane na moim schemacie blokowym. Planuję używać MySQL dla serwera i SQLite dla klienta.
To nie jest gra turowa, więc nie ma wiele interakcji z innymi graczami. Serwer dostarczy listę graczy online i możesz z nimi walczyć klikając przycisk bitwy, a po animacji zostanie ogłoszony wynik.
Do synchronizacji baz danych mam na myśli dwa rozwiązania:
- Przechowuj znacznik czasu dla każdego rekordu. Śledź również datę ostatniej aktualizacji lokalnego DB. Podczas synchronizacji wybierz tylko te wiersze, które mają większy znacznik czasu i wyślij do lokalnej bazy danych. Zachowaj flagę isDeleted dla usuniętych wierszy, aby każde usunięcie zachowywało się jak aktualizacja. Ale mam poważne wątpliwości co do wydajności, ponieważ przy każdym żądaniu synchronizacji musielibyśmy przeskanować całą bazę danych i poszukać zaktualizowanych wierszy.
- Inną techniką może być prowadzenie dziennika każdego wstawiania lub aktualizacji, która ma miejsce w stosunku do użytkownika. Gdy aplikacja kliencka prosi o synchronizację, przejdź do tej tabeli i dowiedz się, które wiersze której tabeli zostały zaktualizowane lub wstawione. Po pomyślnym przesłaniu tych wierszy do klienta usuń ten dziennik. Ale potem myślę o tym, co się stanie, jeśli użytkownik użyje innego urządzenia. Zgodnie z tabelą dzienników wszystkie aktualizacje zostały przesłane dla tego użytkownika, ale tak naprawdę zostało to zrobione na innym urządzeniu. Być może będziemy musieli również śledzić urządzenie. Wdrożenie tej techniki jest bardziej czasochłonne, ale nie jestem pewien, czy zostanie ona wykonana jako pierwsza.
źródło
Odpowiedzi:
Jeśli nie jest to gra „w czasie rzeczywistym”, w tym sensie, że gracze nie muszą widzieć natychmiastowego wyniku działań innego gracza na scenie gry, powinieneś być w porządku z żądaniami HTTP. Ale pamiętaj o kosztach HTTP.
To powiedziawszy, użycie HTTP nie uratuje cię od ostrożnego projektowania protokołu komunikacyjnego. Ale jeśli jesteś odpowiedzialny zarówno za serwer, jak i po stronie klienta, masz szczęście, ponieważ możesz dostosować protokół, gdy go potrzebujesz.
Aby zsynchronizować między główną bazą danych a bazą danych klienta, możesz użyć dowolnego protokołu transportu, do którego masz dostęp, HTTP lub innych. Ważną częścią jest logika synchronizacji. Aby uzyskać proste podejście, po prostu połącz z serwera wszystkie najnowsze zmiany potrzebne klientowi od ostatniej sygnatury czasowej w bazie danych klienta. Zastosuj go do bazy danych klienta i przejdź do tego. Jeśli masz więcej zmian po stronie klienta, prześlij go, jeśli nadal jest istotny, w przeciwnym razie odrzuć.
Niektóre gry nawet nie używają lokalnej bazy danych, po prostu śledzą status, zbierając odpowiednie informacje z serwera, gdy jest to potrzebne.
Jeśli utrata lokalnych zdarzeń jest nie do zaakceptowania, to tak, powinieneś mieć lokalną pamięć i zapisywać w niej tak często, jak to możliwe. Możesz spróbować to zrobić przed wysłaniem każdej sieci.
Aby sprawdzić, czy aktywni użytkownicy używali pingowania HTTP co 20 sekund w udanej grze ... Ta wartość wzrosła natychmiast, ponieważ serwery zostały przeciążone :( Zespół serwerów nie pomyślał o sukcesie. Chciałbym więc dodać wiadomość lub jakiś specjalny nagłówek w protokole komunikacyjnym, który pozwoli Ci ponownie skonfigurować klientów (w celu równoważenia obciążenia ping i innych wartości związanych z komunikacją).
Walidacje po stronie klienta są wystarczające, jeśli nie przeszkadza ci oszustów, hakerów i inne automatyczne skrypty atakujące twoją grę. Serwer może po prostu odrzucić twoje działanie i wysłać komunikat o błędzie z pewnymi szczegółami. Ten komunikat o błędzie jest obsługiwany przez wycofanie zmian lokalnych lub przez niestosowanie ich do pamięci lokalnej, jeśli można poczekać, aż serwer zareaguje na faktyczne zapisanie zmian.
Użyliśmy wzorca poleceń w naszej grze, aby umożliwić proste i wydajne wycofywanie nieudanych lub nieprawidłowych działań użytkownika. Komendy były odtwarzane lokalnie wysyłane do peerów lub tłumaczone na komunikat serwera, a następnie stosowane na peerach lub sprawdzane na serwerze, w przypadku problemu polecenia nie były odtwarzane, a scena gry powróciła do stanu początkowego z powiadomieniem.
Korzystanie z HTTP nie jest złym pomysłem, ponieważ później pozwoli na bardzo łatwą integrację z klientami Flash lub HTML5, jest elastyczny, możesz używać dowolnego rodzaju języka skryptowego serwera, a dzięki podstawowym technikom równoważenia obciążenia możesz później dodać więcej serwerów bez większego wysiłku skalować backend.
Masz dużo do zrobienia, ale to fajna praca ... Ciesz się!
źródło
Najłatwiej jest zaimplementować bazę danych jako pojedynczy plik, który można przesłać. Jeśli próbujesz porównać różnice między bazami danych, to świat pełen bólu, a ja mówię z doświadczenia.
Pamiętaj, że twój serwer nie może ufać decyzjom podejmowanym przez klienta na podstawie tej lokalnej bazy danych, ponieważ klient może to zmienić. Musi istnieć wyłącznie w celu przedstawienia szczegółów.
Nie. Co jeśli serwer zdecyduje, że zdarzenie nigdy się nie wydarzyło? Klient i tak nie powinien podejmować decyzji o zdarzeniach, ponieważ nie można mu ufać.
Zauważyłem, że mówisz także o lokalnej bazie danych na dwa sposoby: jeden dla „rzeczy, które nie zmieniają się często” i dwa dla zdarzeń. Z powyższych powodów nie powinny one znajdować się w tej samej bazie danych - nie chcesz próbować scalać ani różnicować poszczególnych wierszy danych w bazach danych. Na przykład integralność referencyjna staje się problemem, gdy klient ma odniesienie do elementu, który zdecydujesz się usunąć z serwera. Lub jeśli klient zmienia wiersz, a serwer zmienia wiersz, która zmiana ma pierwszeństwo i dlaczego?
Tak, pod warunkiem, że są dość rzadkie lub małe. HTTP nie jest efektywny pod względem przepustowości, więc miej to na uwadze.
Jeśli używasz przejściowego protokołu, takiego jak HTTP, to rozsądny pomysł. Gdy serwer otrzyma wiadomość od klienta, możesz zaktualizować czas „ostatniego obejrzenia” dla tego klienta.
Nie, wcale nie. Klient jest w rękach wroga. Jak cofnąć akcję, zależy całkowicie od tego, co uważasz za akcję i jakie mogą mieć efekty. Najłatwiejszą drogą jest w ogóle nie zaimplementowanie akcji, dopóki serwer nie zareaguje, aby na to zezwolić. Nieco trudniejszą drogą jest upewnienie się, że każda akcja ma możliwość cofnięcia akcji i buforowania wszystkich niepotwierdzonych akcji na kliencie. Gdy serwer je potwierdzi, usuń je z pamięci podręcznej. Jeśli akcja zostanie odrzucona, wycofaj każdą akcję w odwrotnej kolejności, aż do odrzuconej.
źródło