Multi-client, asynchroniczne sockety w c#, najlepsze praktyki? [zamknięte]

Staram się lepiej zrozumieć gniazda tcp / ip w c#, ponieważ chcę rzucić sobie wyzwanie, aby zobaczyć, czy mogę stworzyć działającą infrastrukturę MMO (świat gry, mapa, gracze, itp.) wyłącznie w celach edukacyjnych, ponieważ nie mam zamiaru być kolejnym z tych "OMGZ iz zrobię mój r0x0r MMORPG, który będzie lepszy niż WoW!!!", wiesz o czym mówię.

W każdym razie, zastanawiałem się, czy ktoś może rzucić trochę światła, jak można podejść do projektowania tego rodzaju system i jakie rzeczy są wymagane i na co powinienem uważać?

Moim początkowym pomysłem było podzielenie systemu na oddzielne połączenia klient / serwer przy każdym połączeniu (na własnym porcie) wykonującym określone zadanie, takie jak aktualizacja pozycji gracza/potwora, wysyłanie i odbieranie wiadomości na czacie itp. co dla mnie ułatwi przetwarzanie danych, ponieważ nie zawsze trzeba umieszczać nagłówek na danych, aby wiedzieć, jakie informacje zawiera pakiet.

Robi to ma sens i jest użyteczne, czy jestem po prostu zbyt skomplikowany?

Twoje odpowiedzi są bardzo mile widziane.

Author: Will, 2008-11-12

4 answers

Jeśli programujesz na poziomie gniazd, to niezależnie od liczby portów otwartych dla każdego typu wiadomości, nadal musisz mieć jakiś nagłówek. Nawet jeśli jest to tylko długość reszty wiadomości. Powiedziawszy, że łatwo jest dodać prosty nagłówek i strukturę ogona do wiadomości. Myślę, że łatwiej jest mieć do czynienia tylko z jednym portem po stronie klienta.

Wydaje mi się, że współczesne MMORPG (a może nawet stare) miały dwa poziomy serwerów. Login serwery, które weryfikują Cię jako płatnego klienta. Po zweryfikowaniu przechodzą do serwera gry, który zawiera wszystkie informacje o świecie gry. Mimo to wymaga to tylko, aby klient miał jedno otwarte gniazdo, ale nie uniemożliwia posiadania więcej. Dodatkowo większość gier MMORPG szyfruje wszystkie swoje dane. Jeśli piszesz to jako ćwiczenie dla zabawy, to nie będzie to miało większego znaczenia.

Do projektowania / pisania protokołów w ogóle oto rzeczy, które martwię się o:

Endianess

Czy klient i serwer zawsze mają taką samą endianess. Jeśli nie, muszę się tym zająć w moim kodzie serializacyjnym. Istnieje wiele sposobów radzenia sobie z endianess.

  1. zignoruj to-oczywiście zły wybór
  2. Określ endianness protokołu. To właśnie robiły/robiły starsze protokoły, stąd termin network order, który zawsze był big endian. Nie ma znaczenia, którą endianess określisz tylko, że określasz jedno lub drugie. Niektórzy starzy Programiści sieciowi wpadną w ramiona, jeśli nie użyjesz big endianess, ale jeśli twoje serwery i większość klientów są little endian, naprawdę nie kupujesz sobie niczego innego niż dodatkową pracę, czyniąc protokół big endian.
  3. zaznacz Endianess w każdym nagłówku - możesz dodać plik cookie, który powie Ci endianess klienta / serwera i pozwoli na konwersję każdej wiadomości w razie potrzeby. Praca dodatkowa!
  4. Stwórz swój protokół agnostic - jeśli wysyłasz wszystko jako ciągi ASCII, to endianess jest nieistotne, ale też dużo bardziej nieefektywne.

Z 4 zazwyczaj wybieram 2 i określam endianess, aby być tym Większości Klientów, które teraz dni będą little endian.

Kompatybilność Do Przodu i do tyłu

Czy protokół musi być zgodny do przodu i do tyłu. Odpowiedź powinna prawie zawsze brzmieć tak. W takim przypadku to określi sposób, w jaki projektuję protokół over all pod względem wersjonowania i jak każda pojedyncza wiadomość jest tworzona w celu obsługi drobnych zmian, które naprawdę nie powinny być częścią wersjonowania. Możesz na tym puntować i używać XML, ale tracisz dużo wydajności.

Dla ogólnego wersjonowania Zwykle projektuję coś prostego. Klient wysyła komunikat wersjonujący określający, że mówi w wersji X. Y, O ile serwer może obsługiwać tę wersję, wysyła komunikat potwierdzający wersję klienta i wszystko idzie do przodu. W przeciwnym razie nabija klienta i kończy połączenie.

Do każdej wiadomości masz coś w stylu:

+-------------------------+-------------------+-----------------+------------------------+
| Length of Msg (4 bytes) | MsgType (2 bytes) | Flags (4 bytes) | Msg (length - 6 bytes) |
+-------------------------+-------------------+-----------------+------------------------+

Długość oczywiście mówi ci, jak długa jest wiadomość, nie wliczając samej długości. MsgType jest typem wiadomości. Tylko dwa bajty do tego, ponieważ 65356 to mnóstwo typów wiadomości dla aplikacji. Flagi są tam, aby poinformować, co jest serializowane w wiadomości. Pole to w połączeniu z długością jest co zapewnia kompatybilność do przodu i do tyłu.

const uint32_t FLAG_0  = (1 << 0);
const uint32_t FLAG_1  = (1 << 1);
const uint32_t FLAG_2  = (1 << 2);
...
const uint32_t RESERVED_32 = (1 << 31);

Wtedy twój kod deserializacji może zrobić coś takiego:

uint32 length = MessageBuffer.ReadUint32();
uint32 start = MessageBuffer.CurrentOffset();
uint16 msgType = MessageBuffer.ReadUint16();
uint32 flags = MessageBuffer.ReadUint32();

if (flags & FLAG_0)
{
    // Read out whatever FLAG_0 represents.
    // Single or multiple fields
}
// ...
// read out the other flags
// ...

MessageBuffer.AdvanceToOffset(start + length);

Pozwala to dodawać nowe pola na końcu wiadomości bez konieczności zmiany całego protokołu. Zapewnia również, że stare serwery i klienci zignorują flagi, o których nie wiedzą. Jeśli będą musieli użyć nowych flag i pól, po prostu zmienisz ogólny protokół wersja.

Użyj pracy z Ramką lub nie

Istnieją różne struktury sieciowe, które rozważyłbym użyć w aplikacji biznesowej. Jeśli nie miałem konkretnej potrzeby drapania, wybrałbym standardowe ramy. W Twoim przypadku chcesz nauczyć się programowania na poziomie socket, więc jest to pytanie, na które już odpowiedziałeś.

Jeśli ktoś używa frameworka upewnij się, że rozwiązuje dwa powyższe problemy, lub przynajmniej nie wchodzi ci w drogę, jeśli chcesz go dostosować w tych miejsca.

Czy mam do czynienia z osobą trzecią

W wielu przypadkach możesz mieć do czynienia z zewnętrznym serwerem / klientem, z którym musisz się komunikować. Oznacza to kilka scenariuszy:

  • mają już zdefiniowany protokół - po prostu użyj ich protokołu.
  • masz już zdefiniowany protokół (i chcą go użyć) - ponownie użyj zdefiniowanego protokołu
  • używają standardowego frameworka (opartego na WSDL itp.) - używają ramy.
  • żadna ze stron nie ma zdefiniowanego protokołu-spróbuj określić najlepsze rozwiązanie na podstawie wszystkich czynników (wszystkich wymienionych tutaj), a także ich poziomu kompetencji (przynajmniej o ile możesz powiedzieć). Niezależnie od tego upewnij się, że obie strony zgadzają się i rozumieją protokół. Z doświadczenia wynika, że może to być bolesne lub przyjemne. To zależy od tego, z kim pracujesz.

W obu przypadkach nie będziesz pracować z osobą trzecią, więc to naprawdę po prostu dodano dla kompletności.

Wydaje mi się, że mógłbym napisać o tym znacznie więcej, ale jest to już dość długie. Mam nadzieję, że to pomoże i jeśli masz jakieś konkretne pytania, zadaj je na Stackoverflow.

An edit to answer knoopx ' s pytanie:

 30
Author: grieve,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-30 14:18:38

Myślę, że musisz się czołgać przed chodzeniem i przed biegiem. Najpierw popraw projekt frameworka i połączeń, a następnie martw się o skalowalność. Jeśli twoim celem jest nauczenie się programowania C# i TCP/ip, nie utrudniaj sobie tego.

Kontynuuj swoje początkowe myśli i trzymaj strumienie danych osobno.

Miłej zabawy i powodzenia

 2
Author: Tim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-12 20:13:45

Odradzam wielokrotne połączenia dla różnych informacji. Chciałbym zaprojektować jakiś protokół, który zawiera nagłówek z informacjami do przetworzenia. Wykorzystaj jak najmniej zasobów. Ponadto możesz nie chcieć wysyłać aktualizacji dla różnych pozycji i statystyk z Klienta na serwer. W przeciwnym razie możesz znaleźć się w sytuacji, w której użytkownik może zmodyfikować swoje dane, wysyłając je z powrotem na serwer.

Powiedzmy, że użytkownik fałszuje lokalizację potwora, aby coś ominąć. Ja bym aktualizuje tylko wektor położenia użytkownika i działania. Niech reszta informacji zostanie przetworzona i zatwierdzona przez serwer.

 1
Author: Nicholas Mancuso,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-12 18:51:41

Tak myślę, że masz rację co do pojedynczego połączenia, i oczywiście klient nie będzie wysyłać żadnych rzeczywistych danych do serwera, bardziej jak zwykłe polecenia, takie jak "move forward", "turn left", itp. serwer przenosiłby postać na mapie i wysyłał nowe współrzędne z powrotem do klienta.

 0
Author: Jason Miesionczek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-12 18:56:18