Pomoc w zakresie wymiany komunikatów między klientem a serwerem oraz synchronizacji zegara

10

Robię szybką grę fizyki, która jest hokeja stołowego. Z dwoma młotkami i jednym krążkiem. Gra działa na iPhonie / iPadzie i robię część dla wielu graczy za pośrednictwem GameCenter.

Tak działa system sieciowy. Klient, który oznaczy dopasowanie jako gwiazdkę, zostanie rozliczony jako serwer, a tym, który akceptuje żądanie dopasowania, jest klient.

„Serwer” ma uruchomioną fizykę, a odpowiedź jest natychmiastowa, a klient ma również uruchomioną fizykę, dzięki czemu wygląda płynnie między wymianą wiadomości. Jako serwer robię to, że przesyłam klientowi moją prędkość krążka i moją pozycję, a klient dostosowuje swoją prędkość krążka / pozycję związaną z serwerem, aby zachować synchronizację. W przeciwnym razie fizyka desynchronizuje się i psuje to.

Gdy opóźnienie sieci jest dobre, poniżej 100 ms wyniki są całkiem dobre, mam płynną grę po stronie klienta, a dziwne zachowanie jest minimalne. Problem występuje, gdy opóźnienie wynosi około 150 do 200 ms. W takim przypadku zdarza się, że krążek mojego klienta już uderzył o krawędź i odwrócony kierunek, ale odbiera komunikat o opóźnieniu z serwera i cofa się nieco, powodując dziwne odczucie zachowania piłki.

Przeczytałem o tym kilka rzeczy:

Przykład sieci pongowej

Kliknij Przykład synchronizacji

Wikipedia na temat synchronizacji zegara

Jak mogę to rozwiązać? O ile przeczytałem najlepszą opcją, którą mam, jest synchronizacja zegara na serwerze / kliencie ze znacznikiem czasu, aby po otrzymaniu komunikatów opóźnień związanych z moim zegarem po prostu zignorowałem to i pozwoliłem symulacji klienta wykonać praca. Zgadzacie się z tym? A ponieważ wysyłam niewiarygodne dane (UDP), mogę otrzymywać wiadomości opóźnione lub wiadomości nieczynne.

Jeśli jest to najlepsze podejście, jak wdrożyć synchronizację zegara. Przeczytałem kroki, jak to zrobić, ale nie do końca to zrozumiałem.

Tu jest napisane:

  1. Klient stempluje bieżący czas lokalny na pakiecie „żądanie czasu” i wysyła do serwera.
  2. Po otrzymaniu przez serwer serwer stempluje czas serwera i zwraca
  3. Po otrzymaniu przez klienta klient odejmuje bieżący czas od czasu wysłania i dzieli przez dwa, aby obliczyć opóźnienie. Odejmuje bieżący czas od czasu serwera, aby określić różnicę czasu między klientem a serwerem, i dodaje opóźnienie o połowę, aby uzyskać prawidłową różnicę zegara. (Jak dotąd ten algothim jest bardzo podobny do SNTP)
  4. Klient powtarza kroki od 1 do 3 pięć lub więcej razy, za każdym razem wstrzymując kilka sekund. Tymczasowo dozwolony może być inny ruch, ale w celu uzyskania najlepszych rezultatów należy go zminimalizować. Wyniki odbioru pakietów są gromadzone i sortowane według kolejności od najniższego do największego opóźnienia. Mediana opóźnienia jest określana przez wybranie próbki punktu środkowego z tej uporządkowanej listy.
  5. Wszystkie próbki powyżej około 1 odchylenia standardowego od mediany są odrzucane, a pozostałe próbki są uśredniane przy użyciu średniej arytmetycznej.

Po tym przykładzie chciałbym to:

Udawajmy, że gra się załadowała, a mój czas klienta wynosi teraz 0, więc wysyłam na serwer, że mój czas wynosi 0.

Dostanie się do serwera zajmuje 150 ms, ale zegar serwera już się zaczął i jest o 1 sekundę przed klientem. Kiedy serwer otrzyma wiadomość, czas wyniesie: 1.15 i wyśle ​​ten czas do klienta, czy jesteśmy w porządku? Udawajmy, że nasze opóźnienie jest stałe przy 150 ms.

Teraz klient odbiera czas 1.15 i odejmuje bieżący czas od czasu wysłanego i dzieli przez dwa, aby obliczyć opóźnienie. Które wynosi: 0,3 - 0 = 0,3 / 2 -> 150 ms.

Odejmuje bieżący czas od czasu serwera, aby określić deltę czasu klient-serwer, i dodaje pół opóźnienia, aby uzyskać poprawną deltę zegara:
Czas klienta: 0,3 Czas serwera 1,15
0,3 - 1,15 = 0,85 + opóźnienie (.15) = 1

Jak to jest zsynchronizowane? Czego mi brakuje?

To mój pierwszy raz w trybie dla wielu graczy i sieci, więc jestem trochę zdezorientowany.

Dziękuję Ci.

gmemario
źródło
Dobre pytanie. Czy możesz poprawić: 0,3 - 1,15 to 1,15 - 0,3?
Ciaran

Odpowiedzi:

12

Algorytm opublikowany był poprawny, ale w twoim przykładzie zapominasz o czasie, jaki zajmuje pakiet serwera, aby dotrzeć do klienta, więc:

Server time: 1
Client time: 0
Client sends 0 to server

... 150ms to get to server  (ping is 300! not 150ms in this case. Ping is round-trip)

Server time: 1.15
Client time: 0.15
Server receives packet and sends client 1.15

... 150ms to get back to client

Server time: 1.30
Client time: 0.30
Client receives 1.15 from server

Teraz, jak widać, jeśli klient zmieni zegar na 1,15, będzie o 0,15 za serwerem, dlatego musisz dostosować się do Pinga (inaczej Round Trip Time [RTT]). Oto pełne obliczenie czasu delta na wielu etapach:

Server Time - Current Time + Ping / 2
= Server Time - Current Time + (Current Time - First Packet Time) / 2
= 1.15 (Perceived, not actual!) - 0.30 + (0.30 - 0.00) / 2
= 1.00

To daje nam prawidłowy czas delta wynoszący 1,00 sekundy

John McDonald
źródło
Dostaję to, więc mój zegar klienta ma 1,00, a serwer 1,30 Czy po otrzymaniu wiadomości z serwera muszę dodać polecenie ping, aby sprawdzić, czy wiadomość jest opóźniona? Inna sprawa, a co jeśli opóźnienia ulegną zmianie, czy muszę ciągle robić te łydki przez cały czas?
gmemario,
Czas delta wynoszący 1,00 s należy dodać do bieżącego zegara, aby czas klienta był równy czasowi serwera. Opóźnienie zawsze się zmienia, każdy pakiet będzie miał nieco inny RTT. W krótkim czasie, jak w grze, po prostu zsynchronizuję ten czas tylko raz, klient będzie miał dość dobre domysły co do czasu serwera i oba zegary powinny iść naprzód w przybliżeniu w tym samym tempie. Jedynym wyjątkiem byłby pakiet otrzymany z przyszłości. W takim przypadku istnieją 2 rozwiązania: 1) Wykonaj nową synchronizację czasu. 2) Przyspiesz swój zegar, aby pasował do przyszłego zegara
John McDonald
Ok, spójrzmy na tę sytuację, aby się upewnić: Czas serwera: 1s Czas klienta: 0s 150 ms, aby dostać się do serwera Czas serwera: 1,15 s Czas klienta: 0,15 s Serwer wysyła klienta 1,15s 200 ms, aby dostać się do klienta Zatem mój ping wynosi teraz 350 ms. Czy to jest poprawne? Czas serwera: 1,35 Czas klienta: 0,35 Robiąc łydki: 1,15 - 0,35 + (0,35 - 0,00) / 2 Teraz mój deltaTime = 0,975 Jeśli dodam czas delta 0,975 do mojego 0,35, otrzymuję: 1,325 Który to rodzaj desynchronizacji. Czy to też prawda?
gmemario,
@Gilson, to prawda. Jeśli opóźnienie dla dwóch pakietów jest różne (prawie gwarantowane), obliczanie czasu delta klienta nie będzie idealne. Klient nie może być doskonały. W algorytmie opisanym w pytaniu czas delta był obliczany wiele razy i istniała metoda wybierania i uśredniania tych wyników. Wiele metod synchronizacji czasu może zrobić coś takiego, ale są one zwykle przeznaczone dla serwerów o znaczeniu krytycznym i długoterminowych, takich jak giełdy papierów wartościowych. Jeśli chodzi o dokładność czasu potrzebną do krótkiej gry, myślę, że byłoby to przesadą.
John McDonald,
@Gilson, tak długo, jak klient ma rozsądne oszacowanie czasu serwera i oba zegary poruszają się w przybliżeniu z tą samą prędkością, nie powinieneś zauważać żadnych problemów. Gdy klient lub serwer wyśle ​​akcję na drugą stronę, wyśle ​​swój czas lokalny. Po stronie odbierającej wiadomość powinna zawsze znajdować się w przeszłości i należy ją interpretować jako zdarzenie przeszłe. Oznacza to, że potrzebujesz wszystkich informacji w pakiecie, takich jak lokalizacja, prędkość (wielkość + kierunek) i czas. Następnie możesz umieścić krążek tam i wtedy i przenieść krążek z przeszłości do teraźniejszości. Jestem na czacie przy okazji
John McDonald,