Niezawodność potwierdzeń za pomocą UDP

16

Mam pytanie dotyczące UDP. Dla kontekstu pracuję nad grą akcji w czasie rzeczywistym.

Przeczytałem sporo o różnicach między UDP i TCP i wydaje mi się, że rozumiem je całkiem dobrze, ale jest jedna rzecz, która nigdy nie wydawała się poprawna, a jest to niezawodność , a zwłaszcza podziękowania . Rozumiem, że UDP domyślnie nie zapewnia niezawodności (tzn. Pakiety mogą być odrzucane lub wysyłane poza kolejnością). Gdy wymagana jest pewna niezawodność, rozwiązaniem, które widziałem (co ma sens z koncepcyjnego punktu widzenia) jest użycie potwierdzeń (tj. Serwer wysyła pakiet do klienta, a kiedy klient odbiera tę wiadomość, odsyła potwierdzenie do serwera) .

Co dzieje się po odrzuceniu potwierdzenia?

W powyższym przykładzie (jeden serwer wysyła pakiet do jednego klienta) serwer radzi sobie z potencjalną utratą pakietów poprzez ponowne wysyłanie pakietów co ramkę, dopóki nie zostaną odebrane potwierdzenia dla tych pakietów. Nadal możesz napotykać problemy z przepustowością lub komunikatami o nieporządku, ale wyłącznie z punktu widzenia utraty pakietów serwer jest objęty.

Jeśli jednak klient wyśle ​​potwierdzenie, które nigdy nie dotrze, serwer nie będzie miał innego wyjścia, jak ostatecznie przestać wysyłać tę wiadomość, co może przerwać grę, jeśli informacje zawarte w tym pakiecie będą wymagane. Możesz zastosować podobne podejście do serwera (tj. Wysyłać potwierdzenia aż do otrzymania potwierdzenia dla potwierdzenia?), Ale takie podejście spowodowałoby, że będziesz zapętlał się w tę iz powrotem na zawsze (ponieważ będziesz potrzebował potwierdzenia dla potwierdzenia dla potwierdzenia i tak dalej).

Czuję, że moja podstawowa logika jest tutaj poprawna, co pozostawia mi dwie opcje.

  1. Wyślij pojedynczy pakiet potwierdzenia i miej nadzieję na najlepsze.
  2. Wyślij garść pakietów potwierdzających (może 3-4) i licz na najlepsze, zakładając, że nie wszystkie zostaną odrzucone.

Czy istnieje odpowiedź na ten problem? Czy zasadniczo coś nie rozumiem? Czy jest jakaś gwarancja używania UDP, o której nie wiem? Waham się, czy iść naprzód z nadmiarem kodu sieciowego, dopóki nie poczuję się dobrze, że moja logika jest dobra.

Grimelios
źródło
11
Być może brakuje Ci pojęcia „przekroczenia limitu czasu” i „ponownych prób”.
Kromster mówi, że popiera Monikę
Mogę być pewny. Sugerujesz, że moja logika jest poprawna i, żeby nie zabrzmieć zbyt negatywnie, ale podczas programowania sieci nie mogę zakładać żadnych gwarancji na praktycznie każdą informację w sieci? W trakcie gry w czasie rzeczywistym jest to mnóstwo potencjalnie upuszczonych informacji, co jest w porządku, ale chcę się tylko upewnić, że rozumiem problem.
Grimelios,
10
Żadnych gwarancji. Dobrze. Nigdy nie uwzględniaj „nadziei” w swoich algorytmach. Muszą poradzić sobie z KAŻDYMI pechowymi kombinacjami. PS Po prostu przełączyliśmy się na TCP w naszym RTS, gdzie wszystko jest załatwione, ponieważ potrzebujemy niezawodnej komunikacji (do symulacji lockstep).
Kromster mówi o wsparciu Monice
5
Użyj TCP, gdy potrzebna jest niezawodność, użyj UDP, gdy to nie ma znaczenia. Na przykład współrzędne gracza są wysyłane w mojej grze przez UDP. Używam interpolacji i wygładzania, aby wygładzić brakujące pakiety. działa jak marzenie. rzeczy, które naprawdę muszą być niezawodne, ale mogą być nieco wolniejsze, są wysyłane przez TCP. Jeśli masz stan, w którym nowszy stan unieważnia stary stan, UDP jest dobrym wyborem, ponieważ nie ma znaczenia, kiedy coś pośredniego zostało upuszczone 8 np. pozycja gracza).
Polygnome,
To nie jest bezpośrednia odpowiedź na twoje pytanie, ale zdecydowanie zalecam wymaganie potwierdzenia w grze w czasie rzeczywistym, gdy są one absolutnie konieczne (np. Przy pierwszym połączeniu). O wiele łatwiej (i solidnie) jest zaprojektować zarówno klienta, jak i serwer, tak aby „pracowali z tym, co mają”, dopóki nie otrzymają nowego pakietu w systemie bezstanowym, jeśli to możliwe. Quake 3 zrobił to niesamowicie dobrze z systemem opartym na migawkach . Również biblioteki takie jak Enet mogą niezawodnie wysyłać tylko niektóre pakiety, w tych przypadkach, gdy naprawdę ich potrzebujesz
jrh

Odpowiedzi:

32

Jest to forma problemu dwóch generałów i masz rację - nie ma wystarczającej liczby ponownych prób, aby idealnie zagwarantować odbiór.

W praktyce w grach zwykle istnieje horyzont czasowy, poza którym informacje tak naprawdę nie mają znaczenia, nawet jeśli technicznie docierają one niezawodnie. Podobnie jak w przypadku stwierdzenia, że ​​masz 2 sekundy temu idealny strzał w głowę w szeregu - jest już za późno, aby gracz mógł teraz użyć tych informacji.

Jeśli Twoja utrata pakietów jest tak duża, że ​​nie możesz rutynowo uzyskać potrzebnych informacji przez ciasne okno reakcji, to w przypadku gry w czasie rzeczywistym lepiej byłoby kopnąć gracza i spróbować znaleźć dla nich lepsze dopasowanie w innym miejscu niż kontynuuj próby wysłania pakietu w celu emulacji niezawodnego połączenia.

Z tego powodu niektóre systemy replikacji gier całkowicie pomijają potwierdzanie i ponawianie prób i wybierają spamowanie najnowszej aktualizacji tak często, jak to możliwe. Jeśli ktoś zostanie upuszczony lub spóźni się, szkoda, pomiń go, podnieś następny i kontynuuj, korzystając z systemów przewidywania i interpolacji, aby wygładzić lukę i zminimalizować czkawkę widoczną dla gracza.

Nagle chcę zacząć nazywać to „replikacją Simba”, ponieważ ignoruje problemy z przeszłości i próbuje żyć w chwili obecnej. ;)

Rafiki ustanowił reductio ad absurdum na temat tej filozofii życia

Rozwiązaniem hybrydowym jest ściganie się z wyprzedzeniem, wysyłając nową aktualizację ORAZ (ponieważ aktualizacje stanu gry często mogą być dość małe / nadające się do kompresji ) również spakować ostatnią aktualizację, a może poprzednią ... Więc na wypadek gdyby klient przegapił , nie musisz czekać pełnego czasu podróży w obie strony, aby się dowiedzieć i naprawić. Przez większość czasu klient już to widział, więc w ten sposób są nadmiarowe dane, ale opóźnienie korekty pominiętej wiadomości jest niższe. Aktualizacje klienta mogą zawierać numer indeksu najnowszej kolejnej aktualizacji, którą widzieli, dzięki czemu możesz być minimalnie konserwatywny, ile starych aktualizacji umieścisz w następnym pakiecie aktualizacji.

Można również zaimplementować dwupoziomowy system jako inny rodzaj hybrydy, w którym stan krótkotrwały jest replikowany w niewiarygodny sposób szybkiego strzelania, a stan długoterminowy jest synchronizowany niezawodnie, przy użyciu protokołu TCP lub własnej implementacji niezawodności o wysokiej próbie liczyć. Jest to jednak bardziej skomplikowane w zarządzaniu, ponieważ masz dwa systemy przesyłania wiadomości do utrzymania, a dwie migawki mogą nie być zsynchronizowane ze sobą, dodając zupełnie nową klasę wielkości liter.

DMGregory
źródło
1
+1, dobrze napisane. Chciałbym tylko podkreślić, że jest to bardziej odpowiednie w przypadku gier akcji / gier w czasie rzeczywistym. Gry TBS i RTS (i niektóre wydarzenia z gry akcji) mają inne spojrzenie na „horyzont czasowy, poza którym informacje tak naprawdę nie mają znaczenia”.
Kromster mówi o wsparciu Moniki
3
Tak, w przypadku gry turowej wyobrażam sobie, że ktoś użyłby TCP, a nie próbowałby nałożyć własną warstwę niezawodności na UDP. ;) Nadal jednak klasyfikuję mikro w RTS jako rodzaj rozgrywki z wymagającym horyzontem czasowym - to podejście hybrydowe może się dobrze przydać, gdy masz zarówno aktualizacje o niskim opóźnieniu dla upału chwili, jak i siatka bezpieczeństwa, która z mocą wsteczną obsługuje krytyczne pominięte zdarzenia, takie jak wydatkowanie zasobów.
DMGregory
Jest to niezwykle pomocne i niejako potwierdza moje początkowe obawy. Dziękuję Ci bardzo.
Grimelios,
2
Warto również wspomnieć o korekcji błędów przekazywania. Zaprojektuj swój protokół tak, aby odbiornik mógł samodzielnie stwierdzić, że pakiet został odrzucony po odebraniu następnego pakietu, dodając dodatkowe dane w celu wygładzenia wymaganej interpolacji. Może to być przydatne, ponieważ często pakiety UDP i tak nie są pełne, a po prostu częściej wysyłane są mniejsze pakiety, aby zmniejszyć opóźnienie. Dodanie dodatkowych bajtów nie zaszkodzi opóźnieniu, a przepustowość nie stanowi w takich przypadkach problemu.
MSalters
@MSalters Powiedziałbym, że warto rozwinąć własną odpowiedź, jeśli masz na to ochotę. Głosowałbym za tym. :)
DMGregory
9

Metoda TCP polega na tym, że nadawca będzie wysyłał pakiet ponownie, dopóki nie otrzyma potwierdzenia. Odbiornik zignoruje zduplikowane pakiety, ale nadal wyśle ​​dla nich potwierdzenia. Nadawca zignoruje duplikaty potwierdzeń.

Jeśli pakiet zostanie zgubiony, nadawca wysyła go ponownie, jak już wiesz.
Jeśli potwierdzenie zostanie utracone, nadawca ponownie wysyła oryginalny pakiet, co powoduje, że odbiorca ponownie wysyła potwierdzenie.

Jeśli potwierdzenie nie zostanie odebrane w określonym czasie (być może 60 sekund lub 20 ponownych prób), gracz uważa się za odłączony od gry. Państwo musi wdrożyć jakieś reguły limitu czasu lub inny gracz, który odłączy ich kabel sieciowy będzie związać się zasobów na serwerze zawsze.

użytkownik253751
źródło
Istotną cechą protokołu TCP jest to, że nadawca nie musi dbać o to, czy jakiś konkretny pakiet został potwierdzony, ale przede wszystkim musi dbać o „znak wysokiej wody” i o to, jak długo pakiety były wyjątkowe bez ruchu znaku wysokiej wody.
supercat
1
@ supercat Nie powiedziałbym, że to konieczne; bardziej jak optymalizacja.
user253751
Jeśli chodzi o rzecz w nawiasach (wysyłanie ACK dla pakietów, które już otrzymałeś), myślę, że powinieneś ją podkreślić zamiast nawiasować. Wydaje się, że brakuje jej zrozumienia PO (lub przynajmniej jego opisu).
Angew nie jest już dumny z SO
@Angew zrobione teraz.
user253751
6

Jeśli chcesz wynaleźć TCP na nowo, warto spojrzeć na TCP najpierw , który dotyczy dokładnie opisanego problemu (część rozwiązania polega na użyciu wartości zdefiniowanych przez użytkownika do prób ponownych prób i przekroczeń limitu czasu).

Rozwiązania wykorzystujące 2 kanały, kanał TCP (do niezawodnej komunikacji) oraz kanał UDP (do komunikacji o niskim opóźnieniu) nie są rzadkie.

Niektóre rozwiązania wykrywają, gdy klient zbyt długo brakuje informacji, i rozpoczynają resynchronizację, która może wykorzystywać UDP lub TCP.

Innym powszechnym podejściem jest takie zaprojektowanie komunikacji, aby w ogóle nie opierała się na potwierdzeniach, ale to nie wchodzi w zakres pytania.

Piotr
źródło
3

W RTS tak naprawdę nie można używać protokołu takiego jak TCP i nie można też uczynić UDP niezawodnym. Jeśli spróbujesz, gra zawiesi się za każdym razem, gdy nastąpi podłączenie do sieci.

Zamiast tego projektujesz protokół tak, aby pominięte pakiety nie miały większego znaczenia.

Krótka wersja jest taka, że ​​nie obchodzi Cię, gdzie inni gracze byli w ostatniej klatce, o ile wiesz, gdzie są teraz . Długa wersja jest bardziej skomplikowana.

Powstaje zatem pytanie: co robisz, gdy brakuje pakietu? A odpowiedź brzmi ... tak myślicie. Gracz prawdopodobnie porusza się w linii prostej, prawda? Po prostu przesuń je o krok dalej wzdłuż tej linii. ... Tyle że żaden gracz RTS nigdy nie porusza się po linii prostej. A potem jest wykrywanie kolizji.

To jest trudne. Wiele gier źle to rozumie. Można argumentować, że nie ma na to właściwej odpowiedzi, tylko różne zło, które można wymienić.

Powodem, dla którego te gry działają całkiem nieźle, jest nie tylko to, że długo i intensywnie zastanawiali się nad tymi problemami, ale także, że Internet stał się dość niezawodny. Prawie wszystkie pakiety UDP faktycznie docierają na miejsce w odpowiednim czasie. (Chyba że istnieje stały problem, taki jak zapora ogniowa)

Stig Hemmer
źródło
Warcraft 3 używa TCP.
fsp