O (nie)zawodności TCP

2008-06-06 20:43

Dowiadując się, czym jest protokół TCP, można przy okazji usłyszeć lub przeczytać, że jest on niezawodny (reliable). Można wtedy pomyśleć, że to jakiś rodzaj białej magii – zwłaszcza, gdy pomyślimy sobie, jakie przeszkody mogą spotkać przesyłany kawałek danych podczas podróży tysiącami kilometrów kabli (a ostatnio i bez nich). Zatory w transmisji, źle skonfigurowane routery, nagła zmiana topologii sieci, i tak dalej… Mimo to niektóre programistyczne interfejsy próbują nam wmówić, że odczyt i zapis danych “w sieci” jest w gruncie rzeczy niemal identyczny np. z dostępem do dysku. To pewnie też skutek “myślenia magicznego” na temat pojęcia niezawodności w kontekście TCP.

A w praktyce nie kryje się za nim żadna magia. Niezawodność TCP ma swoje granice i obsługuje dokładnie tyle możliwych sytuacji i zdarzeń losowych, ile zostało przewidzianych – jawnie lub nie – w specyfikacji tego protokołu. (Zawartej w RFC 793, jeśli kogokolwiek ona interesuje ;]). Analogicznie jest na przykład z dostępem do dysków wymiennych: stare wersje Windows potrafiły radośnie krzyknąć sławetnym bluescreenem, gdy użytkownik ośmielił się wyciągnąć dysk z napędu w trakcie pracy aplikacji, która z niego korzystała. Była to bowiem sytuacja nieprzewidziana na odpowiednio wysokiej warstwie systemu.
Jakie więc niespodziewane sytuacje i problemy dotyczące TCP należy przewidywać, jeśli piszemy programy sieciowe? Jest ich przynajmniej kilka:

  • Pakiety TCP po drodze od nadawcy i odbiorcy mogą ulegać podzieleniu (fragmentacji) w celu przepchnięcia ich przez podsieci o różnych możliwościach przesyłu. Wszystkie kawałki są oczywiście składane przez odbiorcę w miarę ich napływania wraz z dodatkowymi informacjami, pozwalającymi na odtworzenie kolejności bajtów. Jednak fragmentacja sprawia, że nie musimy odebrać danych w porcjach tej samej długości, w jakiej były wysyłane. Dowolna ilość wywołań funkcji typu Send u nadawcy może przekładać się na równie dowolną liczbę wywołań Receive w celu odebrania wysłanych informacji. Stąd wynika konieczność rozróżniania porcji danych we własnym zakresie, o czym pisałem jakiś czas temu.
  • Połączenie TCP może się zakończyć się nieoczekiwanie, jak choćby poprzez nagłe zakończenie wykorzystującego je procesu u jednej ze stron komunikacji. Wykrycie takiej sytuacji polega niestety wyłącznie na opóźnieniach (timeout) w otrzymywaniu pakietów potwierdzających (ACK) dostarczanie danych. To sprawia, że nie można odróżnić zerwania połączenia od zatorów w transmisji na poziomie samego protokołu TCP.
    Dlatego też trzeba sobie z tym radzić na wyższym poziomie, implementując w protokołach aplikacyjnych mechanizmy typu ping-pong do okresowego sprawdzania, czy połączenie “żyje”. Należy przy tym sprawdzać czas odpowiedzi nie względem jakichś twardych limitów, tylko poprzednio rejestrowanych interwałów.
  • Diagram stanów połączenia TCP
    Diagram stanów
    połączenia TCP

    Wreszcie, połączenie TCP można też zakończyć grzecznie (wymianą pakietów z flagami: FIN, FIN/ACK i ACK), co powinno dać się wykryć przez sieciowy interfejs programistyczny. W praktyce bywają z tym problemy, a jako takie powiadamianie o rozłączeniu działa tym lepiej, im niższy poziom API jest używany. W miarę pewnie wygląda to na warstwie POSIX-owych gniazdek (w Windows zaimplementowanych jako biblioteka WinSock) oraz w ich bezpośrednim opakowaniu na platformie .NET (System.Net.Sockets.Socket) lub w Javie (java.net.Socket). Wraz ze wzrostem poziomu abstrakcji (jak chociażby w specjalizacjach “sieciowych” strumieni I/O) sprawa wygląda już nieco gorzej…

Z niezawodnością TCP nie ma co więc przesadzać. W szczególności nie powinniśmy oczekiwać, że zapewni nam ona ochronę przed wszystkimi wyjątkowymi sytuacjami, jakie mogą wydarzyć się podczas komunikacji sieciowej. O przynajmniej kilku musimy pomyśleć samodzielnie.

Tags: , ,
Author: Xion, posted under Internet, Programming »



Adding comments is disabled.

Comments are disabled.
 


© 2019 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.