Autor Wątek: Gra 2d, udp czy tcp?  (Przeczytany 4415 razy)

Offline Galfados

  • Użytkownik

# Kwiecień 14, 2016, 18:39:10
Witam, się zastanawiam jakiego protokołu(nie wiem jak to nazwać) użyć do stworzenia jakiejś prostej w miarę dynamicznej gry (jakaś prosta walka, chodzenie itp.). Czytałem na internecie, że ponoć lepiej użyć udp bo szybsze, ale wtedy jak identyfikować graczy(więcej niż 2)? Myślałem aby dla każdego wygenerować jakiegoś hasha czy coś w ten deseń i po prostu wysyłać z każdym pakietem. Z drugiej strony jest tcp, które jest wolniejsze ale nie ma tego problemu z identyfikacją graczy. I jeszcze dochodzi sprawa chatu, gdzie lepiej by sprawdził się tcp. I tutaj pytanie, którego protokołu lepiej użyć tcp czy udp, a może oba naraz? A jeśli oba naraz to czy można bez problemu nasłuchiwać na jednym porcie tymi dwoma protokołami? Chciałbym to zrobić dobrze, dlatego się was pytam o zdanie.

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline Xion

  • Redaktor
    • xion.log

  • +1
# Kwiecień 14, 2016, 21:09:48
Pakiety UDP -- jak wszystkie ramki IP -- zawierają adresy źródłowe, więc możesz używać ich do identyfikowania graczy.

Do chatu zdecydowanie chcesz TCP, niezależnie od tego jak przesyłasz dane samej rozgrywki.

Dlaczego chcesz nasłuchiwać połączeń TCP i pakietów UDP na tym samym porcie? Niby się da, ale dla samego łatwiejszego debugowania używałbym dwóch innych portów. Masz ich prawie 65 tysięcy do wyboru, co za różnica czy użyjesz jednego czy dwóch?

Offline Galfados

  • Użytkownik

# Kwiecień 14, 2016, 21:38:48
Chcę na tym samym porcie, bo chcę server trzymać na jednym porcie. A co to tych ramek, to nie będzie problemu jeżeli będą łączyć się dwa komputery z tej samej sieci? Albo klienty odpalone na tym samym hoście?

Offline toxic

  • Użytkownik

  • +1
# Kwiecień 14, 2016, 22:13:27
Użyj TCP (jako pierwszego podejścia do problemu).

Jak się okaże, że jest za wolne - dasz rade przepisać moduł sieciowy na UDP.
A jak sie okaże, że TCP wystarczy - super, być może właśnie zaoszczedziłeś kilka-kilkanaście godzin/dni/tygodni, które byś spędził na obkodowywaniu UDP tak, żeby robił co robi TCP od razu z pudełka.

Offline Xender

  • Użytkownik

# Kwiecień 14, 2016, 22:23:17
Pakiety UDP -- jak wszystkie ramki IP -- zawierają adresy źródłowe, więc możesz używać ich do identyfikowania graczy.
"Adres" owszem, ale nie w rozumieniu adresu IP, a instancji struct sockaddr, które w wypadku IPv4 zawiera też port źródłowy, a w wypadku UPv6 inne dodatkowe pola.

Do chatu zdecydowanie chcesz TCP, niezależnie od tego jak przesyłasz dane samej rozgrywki.
Czemu "zdecydowanie"? Mając jakiekolwiek warstwę połączeniową nabudowaną na UDP (która przyda się też do innych rzeczy w grze) można puścić po tym samym.
A nabudowanie własnej warstwy połączeniowej na UDP pozwala uniknąć problemów TCP-a (np. jego congestion control).

Chcę na tym samym porcie, bo chcę server trzymać na jednym porcie.
Z tego, co wiem, porty TCP i UDP mają dwie osobne przestrzenie portów - nie ma czegoś takiego jak TCP i UDP na jednym porcie, port TCP 8000 i UDP 8000 są od siebie niezależne.

A co to tych ramek, to nie będzie problemu jeżeli będą łączyć się dwa komputery z tej samej sieci? Albo klienty odpalone na tym samym hoście?
Ramki należy identyfikować po 4-tuple: adres źródłowy, port źródłowy, adres docelowy, port docelowy.
W przypadku TCP, system operacyjny to załatwia - ma się jeden socket nasłuchujący, na którym w pętli zdarzeń woła się accept(), gdy tylko coś się na nim pojawi. accept() czeka* na przychodzące połączenie TCP (3-way handshake, te sprawy) - i jak takowe zostanie nawiązane, to zwraca nowy socket dla tego konkretnego połączenia.

*Jeśli ma się normalną pętlę z selectem, to nie czeka, tylko przetwarza dane, które już na tym sockecie czekają (wiemy, że czekają akurat na tym, bo select() tak powiedział).

UDP "nie wymaga" pętli z accept() - z drugiej strony, spotkałem się gdzieś choć raz z informacją, że accept() jako takiego nie zabrania.
Używa się np. recvfrom().

https://en.wikipedia.org/wiki/Berkeley_sockets#Client-server_example_using_TCP
https://en.wikipedia.org/wiki/Berkeley_sockets#Client-server_example_using_UDP


--- (TL;DR:) ---

A jak już przebrnąłeś przez te "akademickie" rozważania, to masz gotowca: :)
http://enet.bespin.org/

Offline Xion

  • Redaktor
    • xion.log

# Kwiecień 15, 2016, 07:32:04
Cytuj
"Adres" owszem, ale nie w rozumieniu adresu IP, a instancji struct sockaddr, które w wypadku IPv4 zawiera też port źródłowy, a w wypadku UPv6 inne dodatkowe pola.
Przez "IP" miałem tu na myśli protokół IP i to co znajduje się w jego nagłówku.

Cytuj
A nabudowanie własnej warstwy połączeniowej na UDP pozwala uniknąć problemów TCP-a (np. jego congestion control).
No tak, że o slow start czy 2-way vs. 3-way handshake nie wspomnę. Tylko czy uważasz, że OP potrzebuje (i wie, że potrzebuje) się o to martwić? :)


Offline laggyluk

  • Użytkownik
    • http://laggyluk.com

# Kwiecień 15, 2016, 09:56:06
co do dwóch klientów na tym samym kompie to server może im przypisać różne id które potem będą dodawać do swoich pakietów

Offline Xender

  • Użytkownik

# Kwiecień 15, 2016, 12:07:04
Przez "IP" miałem tu na myśli protokół IP i to co znajduje się w jego nagłówku.
IP nie ma portów, one pojawiają się dopiero na warstwie transportowej*.
Czyli UDP/IP lub TCP/IP, bądź innych.

Na tej warstwie są tylko adresy:
https://en.wikipedia.org/wiki/IPv4#Packet_structure
https://en.wikipedia.org/wiki/IPv6_packet

Na tej warstwie są tylko porty (adresami zajmuje się warstwa internetu*, która leży niżej):
https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure
https://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure

* Używam tu terminologii Internet protocol suite, kompletnie ignorując model protokołów OSI, które były konkurencją dla IP suite - nie bądźmy jak profesorowie-dinozaurowie, nie mieszajmy pojęć.

No tak, że o slow start czy 2-way vs. 3-way handshake nie wspomnę. Tylko czy uważasz, że OP potrzebuje (i wie, że potrzebuje) się o to martwić? :)
Myślę, że nie potrzebuje, ale wspomniałem o tym w kontekście gotowych bibliotek (ENet lub inne) - mając taką, nie musi puszczać chatu osobno po TCP, tylko może jej użyć do wszystkiego.

Ogólnie ja bym się mocno porozglądał za istniejącymi bibliotekami, bo problematyka jest szeroko znana, generyczna i nietrywialna - idealny materiał na libkę. Jestem przekonany, że nie jedna i nie dwie powstały i każdy znajdzie coś dla siebie.


co do dwóch klientów na tym samym kompie to server może im przypisać różne id które potem będą dodawać do swoich pakietów
Będą miały różny port źródłowy, nic nie trzeba dopisywać (choć można).

Tak samo z klientami z 2 różnych komputerów za NAT-em (np. typowym konsumenckim routerem) - router ma własną przestrzeń portów, którą przypisuje różnym połączeniom**.

** Powiecie, że UDP jest bezpołączeniowe, tak samo jak IP - ok, ale zarówno dla TCP/IP jak i UDP/IP router trzyma tabelę "aktywnych połączeń" - widząc, że host z wewnątrz sieci wysłał pakiet z danego adresu wewnątrzsieciowego i portu źródłowego na dany adres i port docelowy (od strony WAN), zapisze tę krotkę 4 wartości w tabeli w RAM.
Jak przyjdzie jakiś pakiet z zewnątrz (z WAN), to NAT sprawdza w tabeli "aktywnych połączeń", czy któryś host z wewnątrz nie wysyłał nic na te docelowe adres i port. Jeśli wysyłał - kieruje pakiet na wewnętrzny adres i port, z którego wcześniej odbywała się komunikacja (zakładając, że to odpowiedź na "połączenie" nawiązane z wewnątrz sieci). Jeśli nie znajdzie w tabeli odpowiednich wpisów ani nie ma odpowiedniej reguły przekierowywania portów - odrzuca pakiet.

Offline hashedone

  • Użytkownik

# Kwiecień 15, 2016, 15:44:45
** Powiecie, że UDP jest bezpołączeniowe, tak samo jak IP - ok, ale zarówno dla TCP/IP jak i UDP/IP router trzyma tabelę "aktywnych połączeń" - widząc, że host z wewnątrz sieci wysłał pakiet z danego adresu wewnątrzsieciowego i portu źródłowego na dany adres i port docelowy (od strony WAN), zapisze tę krotkę 4 wartości w tabeli w RAM.
Jak przyjdzie jakiś pakiet z zewnątrz (z WAN), to NAT sprawdza w tabeli "aktywnych połączeń", czy któryś host z wewnątrz nie wysyłał nic na te docelowe adres i port. Jeśli wysyłał - kieruje pakiet na wewnętrzny adres i port, z którego wcześniej odbywała się komunikacja (zakładając, że to odpowiedź na "połączenie" nawiązane z wewnątrz sieci). Jeśli nie znajdzie w tabeli odpowiednich wpisów ani nie ma odpowiedniej reguły przekierowywania portów - odrzuca pakiet.
A czy jest gwarancja, że to zadziała przy wszystkich dostępnych na rynku routerach i we wszystkich poprawnych home-made konfiguracjach? Pytam bo nie wiem, a też mnie temat interesuje. W szczególności zakładam, że jak router dostaje zbyt duży spam danych, albo po prostu pracuje za długo, to może sobie takie wpisy czyścić i może się potem coś pomieszać (router dostanie "odpowiedź" po UDP, ale w międzyczasie wpis wymaże i pakiet zgubi).

Offline Xion

  • Redaktor
    • xion.log

# Kwiecień 15, 2016, 17:21:41
Cytuj
W szczególności zakładam, że jak router dostaje zbyt duży spam danych, albo po prostu pracuje za długo, to może sobie takie wpisy czyścić i może się potem coś pomieszać (router dostanie "odpowiedź" po UDP, ale w międzyczasie wpis wymaże i pakiet zgubi).
Oczywiście że może sobie taki cache wymazywać. I to nawet nie tylko jeśli chodzi UDP, ale i TCP, bo połączenia TCP to nie jest metalowy łańcuch między komputerami -- jak to często sobie ludzi wyobrażają -- tylko trochę stanu zapisanego na obu końcach.

Nie wiem aczkolwiek, czemu miałoby to doprowadzić do gubienia pakietów. Mapowanie host wewnętrzny -> port to permanentna konfiguracja NAT-a. Otrzymując pakiet na port, o którym informacji nie ma jeszcze w cache'u, router może po prostu zajrzeć do tej konfiguracji.

Offline Xender

  • Użytkownik

# Kwiecień 15, 2016, 17:47:15
@hashedone - Nie ma gwarancji, są tylko dobre chęci. :P

Są różne typy NAT w routerach.
Np. popularne w grach jest zestawianie połączenia p2p dwóch klientów za NAT-em z pomocą serwera tylko w pierwszej fazie. Nazywa to się różnie:
- NAT hole punching
- NAT puchthrough
- UDP hole punching
I często to działa, ale gwarancji nie ma.


UDP z definicji jest bezpołączeniowe, więc nie ma żadnych gwarancji nawet teoretycznie. NAT najpewniej po jakimś czasie nieaktywności stwierdzi, że sprawa jest historyczna i sprzątnie stare wpisy. Zwłaszcza, że w UDP nie ma połączenia, które można by zamknąć, by powiedzieć NAT-owi explicite, że może sprzątnąć rekord.

W TCP w praktyce... sprawa wygląda tak samo, jak z UDP. :)
W idealnym scenariuszu NAT może sprzątnąć rekord, gdy przyjdzie do niego pakiet z flagą FIN, czyli oficjalna informacja o zamknięciu połączenia.
W rzeczywistości zdarzają się i sytuacje "nieoficjalne" - host (cały komputer/system) się scrashuje albo zawiesi, zostanie odcięte od niego zasilanie, kabel ethernet przestanie kontaktować - żadna z tych sytuacji nie powoduje otrzymania przez router informacji o zakończeniu połączenia.
O ile jeśli w międzyczasie druga strona spróbuje coś wysłać, to problem szybko wyjdzie na jaw (brak potwierdzeń od odbiorcy), to w wypadku, gdy i druga strona będzie milczeć, jedyne, co NAT może zrobić, to ztimeoutować połączenie.

Nie wiem, czy TCP samo w sobie ma jakiegoś keepalive'a, którego można po prostu włączyć.
Za to słyszałem o keepalive na poziomie np. HTTP, czyli zatroszczenie się o niego samemu (lub sprawdzenie, czy jest już w jakiejś libce) brzmi jak dobry pomysł.
// EDIT: Znalazłem coś o keepalive w TCP pod Linuxem: http://www.tldp.org/HOWTO/TCP-Keepalive-HOWTO/index.html
Mimo wszystko, jeśli chce się mieć coś działającego w każdych warunkach (pod różnymi i różnie skonfigurowanymi systemami), to keepalive w warstwie aplikacji nadal brzmi jak dobry pomysł.


Mapowanie host wewnętrzny -> port to permanentna konfiguracja NAT-a. Otrzymując pakiet na port, o którym informacji nie ma jeszcze w cache'u, router może po prostu zajrzeć do tej konfiguracji.
Mógłbyś podlinkować jakieś źródło?
Mi się wydaje, że jest to rzecz raczej efemeryczna.

Prawie na pewno nie jest permanentne pomiędzy rebootami routera, bo wtedy nawet adresy hostów przydzielane dynamicznie* przez DHCP są różne.
Jak często rebootowany/wyłączany jest router? W sieciach profesjonalnych, ew. firmowych pewnie bardzo rzadko, w domowych - nawet bardzo często.
Czy jest permanentne w czasie działania routera - mam wątpliwości.

* To nie jest pleonazm, bo często można sobie skonfigurować DHCP tak, że danemu MAC będzie zawsze przydzielał konkretny adres, wpisany podczas konfiguracji, także po reboocie - ale to przypadek szczególny.
« Ostatnia zmiana: Kwiecień 15, 2016, 17:54:57 wysłana przez Xender »

Offline Xion

  • Redaktor
    • xion.log

# Kwiecień 15, 2016, 18:17:21
Cytuj
Mógłbyś podlinkować jakieś źródło?
...Otwórz UI swojego routera i pójdź do sekcji Port Forwarding lub podobnej?

Offline Xender

  • Użytkownik

  • +1
# Kwiecień 15, 2016, 19:42:25
...Otwórz UI swojego routera i pójdź do sekcji Port Forwarding lub podobnej?

No to mówimy o 2 zupełnie różnych rzeczach.

Wszystko, o czym mówiłem w poprzednim poście, to tymczasowe tablice NAT na potrzeby dwustronnej komunikacji w ramach połączeń wychodzących (z LAN do WAN/internetu).

- Host w LAN wysyła pakiet UDP lub otwiera połączenie TCP z IP_LAN_hosta:port_LAN_hosta na IP_WAN_serwera:port_WAN_serwera.
- NAT musi dokonać, jak nazwa wskazuje "translacji adresu sieciowego" (bo od strony WAN ma przydzielone 1 adres, a w LAN ma N hostów, każdy ze swoim adresem).
No więc nowy pakiet leci z IP_WAN_routera:port_na_potrzeby_NAT na IP_WAN_serwera:port_WAN_serwera.

- Kiedy przyjdzie pakiet z IP_WAN_serwera:port_WAN_serwera na IP_WAN_routera:port_na_potrzeby_NAT, to NAT zakłada, że jest to odpowiedź w ramach tego samego połączenia.
- Pakiet po translacji leci z IP_WAN_serwera:port_WAN_serwera na IP_LAN_hosta:port_LAN_hosta i jest routowany w obrębie LAN.

Żeby móc dokonać translacji na pakietach przychodzących, NAT musi przy inicjalizowaniu połączenia wychodzącego (z LAN do WAN) (TCP) lub pierwszym pakiecie wychodzącym (UDP) zapisać sobie w tabeli, jakim 4-krotkom IP_LAN_hosta:port_LAN_hosta -> IP_WAN_serwera:port_WAN_serwera (po stronie LAN) odpowiadają jakie 4-krotki IP_WAN_serwera:port_WAN_serwera -> IP_WAN_routera:port_na_potrzeby_NAT (po stronie WAN).


(Tak przy okazji - NAT nie jest obowiązkową częścią routingu, a "dodatkiem" (nawet nie wiem, czy nie dość "niestandardowym", mimo, że jest tak powszechny) - dlatego w paru miejscach napisałem, że po translacji pakiet routowany jest dalej (do innej podsieci)).
« Ostatnia zmiana: Kwiecień 15, 2016, 19:47:50 wysłana przez Xender »

Offline Xion

  • Redaktor
    • xion.log

# Kwiecień 16, 2016, 01:04:24
Cytuj
- Kiedy przyjdzie pakiet z IP_WAN_serwera:port_WAN_serwera na IP_WAN_routera:port_na_potrzeby_NAT, to NAT zakłada, że jest to odpowiedź w ramach tego samego połączenia.
NAT nic nie musi zakładać. Jedyne co musi zrobić to przepisać nagłówek warstwy 4-tej tak, że wskazywał na endpoint wewnątrz sieci lokalnej.

Cytuj
Żeby móc dokonać translacji na pakietach przychodzących, NAT musi przy inicjalizowaniu połączenia wychodzącego (z LAN do WAN) (TCP) lub pierwszym pakiecie wychodzącym (UDP) zapisać sobie w tabeli, (...)
NAT nic nie musi, poza mapowaniem portów po WANowej stronie switcha do LANowych endpointów (adres IP + port wartstwy 4) o którym wspomniałem wcześniej. Gdyby rzeczona tabela (inicjalizowana przy wychodzących pakietach) była rzeczywiście podstawą działania NATu, to jak serwery za nim ukryte mogłyby w ogóle akceptować połączenia? Skąd na przykład router ma wiedzieć, że przychodzący z zewnątrz pakiet SYN na WANowy port TCP 18880 powinien iść do 10.0.0.42 na TCP 80, jeśli wcześniej nie zostało to określone w konfiguracji samego NATu? Przecież listen() na sockecie sam w sobie nie wysyła żadnych danych, więc opieranie się tylko na pakietach wychodzących nie wystarczy.

Offline Rolek

  • Użytkownik

  • +1
# Kwiecień 16, 2016, 11:42:09
@up
Dynamiczne mapowanie nie nadaje się do serwerów, natomiast statyczne byłoby zbyt uciążliwe dla klientów.
Chyba wszystkie routery obsługujące NAT, obsługują oba mechanizmy mapowania.