9.10. TCP – niezawodny strumień bajtów¶
TCP, czyli Transmission Control Protocol, to druga usługa warstwy transportowej działająca na szczycie IP. O ile UDP najlepiej opisać jako „wyślij pakiet i miej nadzieję”, o tyle TCP to „otwórz połączenie między dwoma punktami końcowymi i traktuj je jak dwukierunkową rurę bajtów, które druga strona na pewno otrzyma, w odpowiedniej kolejności i dokładnie raz”. Większość ruchu w internecie korzysta z TCP, podobnie jak większość tego, co kamera robi w sieci.
9.10.1. Co TCP dodaje do IP¶
TCP robi znacznie więcej niż UDP. Dodaje:
Połączenie. Zanim popłyną jakiekolwiek dane, oba punkty końcowe wymieniają krótki uścisk dłoni, aby uzgodnić, że ze sobą rozmawiają. Połączenie ma stan – „otwarte”, „częściowo zamknięte”, „zamknięte” – który obie strony śledzą.
Niezawodne dostarczanie. Każdy bajt wysłany przez nadawcę jest potwierdzany przez odbiorcę. Wszystko, co nie zostanie potwierdzone w określonym czasie, jest wysyłane ponownie. Aplikacja nigdy nie widzi utraconego bajtu; widzi jedynie opóźnienie, podczas gdy protokół ponawia wysyłanie.
Dostarczanie w kolejności. Bajty docierają w tej samej kolejności, w jakiej zostały wysłane. Nawet jeśli pakiety dotrą do odbiorcy w niewłaściwej kolejności, TCP zmienia ich kolejność na właściwą, zanim aplikacja je odczyta.
Kontrola przepływu. Jeśli odbiorca jest powolny, nadawca otrzymuje polecenie zwolnienia; połączenie dostosowuje się do tempa słabszej strony.
Kontrola przeciążenia. Jeśli sieć pośrednia zaczyna gubić pakiety, nadawca wycofuje się, dopóki sytuacja się nie ustabilizuje. Dzięki temu żadne pojedyncze połączenie nie zablokuje przeciążonego łącza.
Wszystko to dzieje się automatycznie. Interfejs API Pythona, którego używa aplikacja, to po prostu send(bytes) i recv(n); TCP obsługuje całą resztę pod spodem.
9.10.2. Uścisk dłoni (handshake)¶
Połączenie TCP otwiera się trójstopniową wymianą, zanim jakiekolwiek dane mogą przez nie przejść:
Trójstopniowy uścisk dłoni. Gdy obie strony już potwierdziły, połączenie jest otwarte i dane mogą płynąć.¶
Klient wysyła pakiet SYN (synchronise) z prośbą o otwarcie. Serwer odpowiada SYN-ACK (synchronise + acknowledge), akceptując prośbę. Klient wysyła końcowe ACK, aby potwierdzić. Po tej rundzie obie strony zgadzają się, że połączenie jest otwarte, i zsynchronizowały swoje liczniki służące do śledzenia, które bajty zostały wysłane i odebrane.
Uścisk dłoni kosztuje jeden czas podróży w obie strony (round-trip-time) opóźnienia, zanim przejdzie pierwszy użyteczny bajt. W sieciach lokalnych to milisekunda; w przypadku połączeń międzykontynentalnych to mniej więcej 100 ms. To główny koszt TCP i powód, dla którego krótkie, jednorazowe wiadomości czasem lepiej wysyłać przez UDP.
9.10.2.1. Uścisk dłoni zamykający¶
Połączenia TCP zamyka się również wymianą (po jednym FIN z każdej strony). Każda ze stron może niezależnie zamknąć swoją połowę połączenia; stan częściowo zamknięty, w którym jedna strona skończyła wysyłać, ale druga wciąż mówi, jest dozwolony, choć rzadko spotykany. Aplikacja zwykle po prostu wywołuje close() i pozostawia protokołowi obsługę sekwencji zamykania.
9.10.3. Czego TCP nie gwarantuje¶
Kilka rzeczy, które czasem zakłada się, że TCP robi, a których nie robi:
Granic wiadomości. Aplikacja wysyła strumień bajtów, a nie strumień wiadomości. Dwa wywołania
send(b"hello")mogą dotrzeć jako jednorecv()zwracająceb"hellohello"albo jako dwa wywołaniarecv()o różnych rozmiarach. Jeśli aplikacja chce ramkowania wiadomości, musi dodać ramkowanie sama (znak nowej linii, prefiks z długością, cokolwiek). Wysyłanie dokumentów JSON przez TCP wymaga na przykład oddzielenia każdego dokumentu znakiem nowej linii lub innym znacznikiem.Szyfrowania. TCP przenosi bajty przekazane mu przez aplikację, w postaci jawnej, przez całą drogę. Jeśli zawartość ma być poufna, aplikacja musi opakować połączenie w TLS (zobacz Szyfrowane gniazda i TLS).
Uwierzytelniania. TCP dba o to, by bajty dotarły nienaruszone. Nie mówi nic o tym, kto je wysłał. Uwierzytelnianie również należy do wyższej warstwy.
Wykrywania aktywności w cichym połączeniu. Jeśli żadna ze stron przez długi czas nie wysyła danych, połączenie jest technicznie nadal otwarte, ale nie potrafi wykryć, że drugi koniec uległ awarii lub zniknął. Aby to naprawić, gdy ma to znaczenie, można włączyć na gnieździe sondy keepalive.
9.10.4. Kiedy używać TCP¶
TCP jest właściwą odpowiedzią dla niemal każdej konwersacji pasującej do schematu „klient otwiera połączenie z serwerem, wymieniają trochę bajtów, połączenie zamyka się po zakończeniu”. Żądania HTTP i HTTPS, sesje SSH, zapytania do baz danych, transfery plików, przesyłanie obrazów – wszystko przez TCP.
Po UDP sięgaj tylko wtedy, gdy konwersacja nie pasuje do tego schematu: niezależne, samowystarczalne wiadomości, w których utrata jest akceptowalna, ruch multicast, synchronizacja czasu, wyszukiwanie nazw lub skrajnie wrażliwe na opóźnienia przypadki, w których koszt uścisku dłoni jest nie do przyjęcia.
Mając na stole porty, UDP i TCP, opowieść o warstwie transportowej jest zakończona. Interfejs API Pythona, który udostępnia oba, znajduje się na następnej stronie: Obiekty gniazd.