Nieblokujący UDP czy osobny wątek do odbioru?

10

Tworzę grę wieloosobową (dla mniej niż 64 graczy). Zdecydowałem już, że mam osobny wątek dla pętli sieciowej, ale zastanawiałem się, czy lepiej byłoby utworzyć dodatkowy wątek do odbierania UDP, czy ustawić gniazdo odbiorcze na nieblokujące (bez dodatkowego wątku).

A może lepiej jest użyć innej metody, takiej jak gniazda asynchroniczne? Lepsze metody są zawsze mile widziane!

Yannick Lange
źródło

Odpowiedzi:

6

Dobra, po pierwsze, jeśli coś masz i działa, zazwyczaj dobrym pomysłem jest pozostawienie tego w ten sposób. Po co naprawiać to, co nie jest zepsute?

Ale jeśli masz problemy i naprawdę chcesz przepisać kod sieci, myślę, że masz cztery główne opcje:

  1. Wielowątkowy kod blokujący (co teraz robisz)
  2. Gniazda nieblokujące z powiadomieniem wyzwalanym poziomem
  3. Gniazda nieblokujące z powiadomieniem o zmianie gotowości
  4. Gniazda asynchroniczne

Po napisaniu wielu klientów i serwerów dla wielu graczy (od peer-to-peer do masowo multiplayer) lubię myśleć, że opcja 2 prowadzi do najmniejszej złożoności, z całkiem dobrą wydajnością, zarówno dla części serwerowej, jak i klienckiej. W drugiej chwili wybrałbym opcję 4, ale zwykle wymaga to ponownego przemyślenia całego programu, i w większości uważam, że jest to przydatne dla serwerów, a nie klientów.

W szczególności chciałbym odradzać blokowanie gniazd w środowisku wielowątkowym, ponieważ zazwyczaj prowadzi to do blokowania i innych funkcji synchronizacji, które nie tylko znacznie zwiększają złożoność kodu, ale mogą również obniżać jego wydajność, ponieważ niektóre wątki czekają na inni

Ale przede wszystkim większość implementacji gniazd (a przy tym większość implementacji I / O) nie blokuje na najniższym poziomie. Operacje blokujące są po prostu przewidziane, aby uprościć tworzenie trywialnych programów. Gdy gniazdo blokuje, procesor w tym wątku jest całkowicie bezczynny, więc po co budować abstrakcję nieblokującą nad abstrakcją blokującą nad zadaniem już nieblokującym?

Nieblokujące programowanie gniazd jest nieco zniechęcające, jeśli jeszcze tego nie próbowałeś, ale okazuje się, że jest dość proste, a jeśli już odpytujesz dane wejściowe, masz już nastawienie do wykonywania gniazd nieblokujących.

Pierwszą rzeczą, którą chcesz zrobić, to ustawić gniazdo na nieblokujące. Robisz to z fcntl().

Po tym, zanim to zrobisz send(), recv(), sendto(), recvfrom(), accept()( connect()jest to inny bit) lub innych połączeń, które mogłyby zablokować wątek, zadzwonić select()na gniazda. select()informuje, czy na gnieździe można wykonać kolejną operację odczytu lub zapisu bez jego blokowania. W takim przypadku możesz bezpiecznie wykonać żądaną operację, a gniazdo się nie zablokuje.

Włączenie tego do gry jest dość proste. Jeśli masz już pętlę gry, na przykład taką:

while game_is_running do
    poll_input()
    update_world()
    do_sounds()
    draw_world()
end

możesz to zmienić, aby wyglądać tak:

while game_is_running do
    poll_input()
    read_network()
    update_world()
    do_sounds()
    write_network()
    draw_world()
end

gdzie

function read_network()
    while select(socket, READ) do
        game.net_input.enqueue(recv(socket))
    end
end

i

function write_network()
    while not game.net_output.empty and select(socket, WRITE) do
        send(socket, game.net_output.dequeue())
    end
end

Jeśli chodzi o zasoby, jedyną książką, którą, jak sądzę, każdy musi mieć na półkach, nawet jeśli jest to jedyna książka, jaką mają, jest „ Unix Network Programming, tom 1 ” zmarłego Richarda Stevensa. Nie ma znaczenia, czy wykonujesz programowanie w systemie Windows, innym systemie operacyjnym lub języku. Nie myśl, że rozumiesz gniazda, dopóki nie przeczytasz tej książki.

Innym źródłem, w którym można znaleźć ogólny przegląd dostępnych rozwiązań w zakresie programowania z wieloma gniazdami (głównie istotne dla programowania serwera), jest ta strona .

Panda Piżama
źródło
Właśnie uruchomiłem silnik gry, więc przepisywanie nie stanowi problemu. Ta odpowiedź była bardzo przydatna i dziękuję za rekomendację książki. Czy znasz też książkę, która bardziej szczegółowo dotyczy gier sieciowych?
Yannick Lange
3
@YannickLange: Nie, ale nie zalecałbym szukania książek specyficznych dla gry, ponieważ tworzenie sieci jest dość niezależne od gatunku, a także żadna inna książka nie jest na poziomie książki Stevensa. Uznanie za korzystanie z UDP, ponieważ używanie protokołu zorientowanego na wiadomości jest dobrym pomysłem podczas pisania programów zorientowanych na wiadomości (jak większość gier). Wiele osób kończy pisanie abstrakcji wiadomości za pośrednictwem protokołu TCP, który sam w sobie jest abstrakcją zorientowaną na strumień, opartą na protokole zorientowanym na wiadomości (IP).
Panda Pajama
-1

Nie napisałeś, którego języka programowania używasz, ale większość języków zapewnia dobre asynchroniczne struktury gniazd, które często wykorzystują funkcje podstawowego systemu operacyjnego.

Kiedy piszesz własną implementację wątkową opartą na blokujących gniazdach (pamiętaj: gdy masz wielu klientów, potrzebujesz wątku dla każdego gniazda), po prostu odkryjesz na nowo to, co już oferuje asynchroniczna struktura gniazd.

Polecam więc korzystanie z gniazd asynchronicznych.

Philipp
źródło
4
dlaczego potrzebuje nici do każdego gniazda? Zwłaszcza z gniazdami asynchronicznymi? Wiele serwerów wagi ciężkiej używa modelu „puli wątków” do przetwarzania danych gniazd, w których każdy wątek obsługuje N klientów i są one odradzane lub zabijane w razie potrzeby. Pomyśl tylko, że ta odpowiedź wymaga poprawy: D
Grimshaw
@Grimshaw Myślę, że źle zrozumiałeś moją odpowiedź. Wydaje mi się, że wyjaśniłem teraz, że podczas korzystania z gniazd blokujących wymagany będzie model wielowątkowy.
Philipp
@Philipp Nie powiedziałem, którego języka programowania używam, ponieważ uważałem, że to nie jest tak naprawdę związane z pytaniem (przy okazji używam c ++). Dziękuję za zalecenie użycia gniazda asynchronicznego. Czy znasz zalecaną książkę / stronę internetową do nauki tego rodzaju metod w grze (silnikach)?
Yannick Lange
@Grimshaw: A jak każdy Nklient obsługuje klientów? Jeśli blokujesz i chcesz mieć pewność, że każde gniazdo zostanie przetworzone, niezależnie od stanu pozostałych gniazd, musisz mieć jeden wątek na gniazdo. To lub nie używaj gniazd blokujących.
Panda Pajama
Mój komentarz dotyczył pierwszej wersji twojej odpowiedzi, w międzyczasie dokonałeś edycji i poprawiłeś ją, zapomnij o tym, co powiedziałem: D
Grimshaw,