Zadanie - Komunikacja klient-serwer

Arek próbuje zacząć bardziej praktyczne podejście do tematów omawianych na wg.net. Jako ze pomysł mi się podoba, to może teraz ja opisze pewien problem na który się natknąłem.

Problem

Mamy układ klient-serwer z komunikacja po TCP/IP. Czyli serwer otwiera gniazdko (Socket lub TcpListener), klient łączy się, następuje wymiana wiadomości i klient się rozłącza. A teraz więcej szczegółów.

  1. Wiadomość to poprawny dokument XML, można założyć na potrzeby tego przykładu ze to tylko znaki ASCII wiec mapowanie wiadomość ciąg bajtów jest trywialne.
  2. Wymiana wiadomości przebiega tak ze klient wysyła wiadomość do serwera i zaczyna słuchać odpowiedzi. Potem wysyła kolejna wiadomość i nasłuchuje odpowiedzi.
  3. Serwer robi to samo tylko symetrycznie. Najpierw przyjmuje połączenie, potem wczytuje wiadomość od klienta.
  4. Generuje odpowiedz i ja wysyła. Potem znowu nasłuchuje wiadomości od klienta lub końca połączenia.
  5. Serwer oczekuje przy odbiorze tylko bajów wiadomości i sam wysyła tylko bajty wiadomości-odpowiedzi.

Zadanie

Jak wydajnie/sprytnie/prosto zaimplementować cześć kliencka? Chodzi o takie rozwiązaniem, które niekoniecznie będzie jak najbardziej niskopoziomowe, lecz proste i łatwo utrzymywane. Można użyć Socket czy TcpClient. Niech w tym rozwiązaniu szybkość wymiany informacji raczej będzie limitowana ograniczeniami fizycznymi sieci niż jakimś skomplikowanym przetwarzaniem informacji z sieci przez klienta.

Uwagi

  1. Implementacja serwera jest stała i nie można jej zmieniać.
  2. Przykładowa implementacja serwera może być taka ze generuje losowa wiadomość jako odpowiedz o długości od 200 B do 8 kB.
  3. Należy oczekiwać ze czasami (powiedzmy 10%) sytuacji będą opóźnienia na sieci kilkunastu/kilkudziesięciu milisekundowe, czasami parę sekund.
  4. Pytania proszę zadawać w komentarzach.
Opublikowane 27 października 08 09:21 przez Wojciech Gebczyk

Komentarze:

# mgrzeg said on października 28, 2008 02:03:

Wojtek, zapomniales wspomniec o nagrodzie.

# Wojciech Gebczyk said on października 28, 2008 10:38:

ha! Dla dobrego rozwiazania (najlepszego, lecz nie jedynego):

1. Wieczna chwała

2. Tytul MVZC (Most Valuable Zine.Net Contributor)

3. I jedna z 2-3 ksiazkowych propozycji - szczegoly do dogadania.

# arkadiusz.wasniewski said on października 30, 2008 13:18:

Kwestie, o których należy pamiętać:

- Dane przesyłane są protokołem TCP w pakietach, których rozmiar może być różny od rozmiaru przesyłanych danych;

- Protokół może czekać, aż bufor systemowy zostanie zapełniony, albo może wysyłać od razu po wpisaniu danych przez program;

- Ze względu na wydajność połączenia mogą być podtrzymywane przez system.

Serwer - wystarczy klasa TcpListener wraz z metodą BeginAcceptSocket. Samo działanie i budowa aplikacji zależy od wielu czynników:

- Ilość jednoczesnych połączeń;

- Długość trwania przetważania żądania klienta.

Jeśli w szczytowym momencie będzie to kilka połączeń i z krótkim czasem przetważania to można spokojnie skorzystać z systemowej puli połączeń (ThreadPool).

Jeśli połaczeń będzie więcej (kilkanaście) lub będą trwały co najmniej kilka sekund to lepiej skorzystać z wątków. Ponieważ tworzenie wątków trochę trwa dobrze jest zaimplementować własną pulę wątków. Przykład takiej implementacji http://www.bearcanyon.com/dotnet/threadpool.zip.

Osobiście polecałbym jednak asynchroniczne przetważanie żądań klienta. Jeśli jednak wybierzemy ten sposób to nie korzystajmy już z puli systemowej ponieważ zabierzemy w ten sposób zasoby dla przetwarzania asynchronicznego.

Samo przesyłanie danych zaimplementowałbym tak, iż na samym począku przesyłabym liczbę typu int zawierającą rozmiar przesyłanych danych. Dzięki temu będziemy pewni, iż odczytana ilość danych to wszystko co mieliśmy pobrać i uniezależnimy się od zerwań połączenia (zerwanie połączenia powoduje, iż o fakcie tym dowiemy się zazwyczaj dopiero po upłynięciu systemowego timeout-u dla protokołu TCP. W Windows o ile dobrze pamiętam jest to 90 sekund). Jednocześnie będziemy ładowali do XmlDocument strumień danych będąc pewni, iż odczytaliśmy już wszystko co należy. Zyskujemy na wydajności.

Klient - spokojnie można skorzystać z klasy TcpClient. Jeśli połączenie powinno zakończyć się w określonym przedziale czasu to może warto zastanowić się nad właściwościami ReceiveTimeout i SendTimeout. Na pewno przyda się to w czasie testów.

# Wojciech Gebczyk said on października 30, 2008 16:08:

Arek, Jedna uwaga - serwer jest napisany i nie mozna zmieniac, a wysylana jest tylko wiadomosc bez dlugosci.

# arkadiusz.wasniewski said on listopada 3, 2008 12:17:

To najlepiej utworzyć własną klasę opakowującą TcpClient.

# Wojciech Gebczyk said on listopada 3, 2008 13:02:

ale jak to odbierac? Serwer wysyla iles tam bajtow nie zamykajac polaczenia, a my mysimy odebrac all ale nie wiecj bo sie zablokujemy i przejsc od razu do wysylania. Tu jest haczyk, ktory trudno obejsc.

# arkadiusz.wasniewski said on listopada 5, 2008 22:38:

Brak konceptu.. hm..

Komentarze anonimowe wyłączone

About Wojciech Gebczyk

Code Sculptor.