Transport część 2: protokół TCP Sieci komputerowe Wykład 6 Marcin Bieńkowski
Protokoły w Internecie warstwa aplikacji HTTP SMTP DNS NTP warstwa transportowa TCP UDP warstwa sieciowa IP warstwa łącza danych Ethernet PPP 802.11 (WiFi) MPLS warstwa fizyczna kable miedziane światłowód DSL fale radiowe "2
W poprzednim odcinku: niezawodny transport Segmentacja Algorytmy niezawodnego dostarczania danych: stop-and-wait, okno przesuwne nadawcy. Potwierdzanie: go-back-n, selektywne, skumulowane. Kontrola przepływu: okno odbiorcy, odbiorca wysyła rozmiar oferowanego okna reguluje rozmiar okna nadawcy. TCP: okno przesuwne + potwierdzanie skumulowane + kontrola przepływu. "3
Przypomnienie: przesuwne okno nadawcy LAR = Last ACK received LAR + SWS 1 2 3 4 5 6 7 8 9 10 wysłane i potwierdzone wysłane ale niepotwierdzone utrzymujemy dla nich licznik czasu niewysłane Akcje: Otrzymanie ACK sprawdzamy, czy możemy przesunąć okno. Przesunięcie okna wysyłamy dodatkowe segmenty. Timeout dla (niepotwierdzonego) segmentu wysyłamy go ponownie. "4
Przypomnienie: potwierdzanie skumulowane LFRcvd (last frame received) LFRcvd + RWS 1 2 3 5 6 Wysyłanie ACK: Wysyłamy tylko jeśli otrzymamy segment S LFRcvd + RWS W razie potrzeby aktualizujemy LFRcvd (przesuwamy okno w prawo) a następnie wysyłamy ACK dla LFRcvd. "5
Przypomnienie: oferowane okno Odbiorca: Oferowane okno = wolne miejsce w buforze Oferowane okno wysyłane nadawcy (zazwyczaj razem z ACK). Np.: pakiety potwierdzane, ale aplikacja wolno czyta oferowane okno jest małe. 1 2 3 4 6 7 Nadawca: Zmienia SWS (rozmiar swojego okna) na rozmiar oferowanego okna. Nie wysyła danych, na które odbiorca nie ma miejsca. 1 2 3 4 5 6 7 8 9 10 "6
Segment TCP 0 7 8 15 16 23 24 31 port źródłowy port docelowy numer sekwencyjny (numer pierwszego bajtu w segmencie) numer ostatniego potwierdzanego bajtu + 1 offset 000 ECN U-A-P-R-S-F oferowane okno suma kontrolna wskaźnik pilnych danych dodatkowe opcje, np. potwierdzanie selektywne "7
Dzisiaj Programowanie gniazd TCP Implementacja TCP "8
Programowanie gniazd
Interfejs programistyczny warstwa aplikacji Aplikacja korzystąca z TCP Aplikacja korzystąca z UDP interfejs gniazd strumieniowych interfejs gniazd datagramowych warstwa transportowa TCP UDP warstwa sieciowa IP ICMP interfejs gniazd surowych Interfejs programistyczny BSD sockets Przystępne wprowadzenie: Beej's Guide to Network Programming. "10
Komunikacja Komunikacja bezpołączeniowa Strony nie utrzymują stanu. Przykładowo: zwykła poczta. Komunikacja połączeniowa Na początku strony wymieniają komunikaty nawiązujące połączenie. Późniejsza komunikacja wygodniejsza niż w przypadku bezpołączeniowym. Na końcu trzeba zakończyć połączenie. Przykładowo: telefon. "11
Gniazda UDP Gniazdo jest związane z konkretnym procesem. Gniazdo identyfikowane przez lokalny adres IP + lokalny port. Gniazdo nie posiada stanu. Gniazdo nie jest połączone z innym gniazdem. Nie ma różnicy między klientem i serwerem: po pierwszym wywołaniu sendto() gniazdo klienta otrzymuje od jądra numer portu i zachowuje się identycznie jak gniazdo serwera. "12
Gniazda TCP: dwa typy gniazd TCP: gniazda nasłuchujące Dla serwera, tylko do nawiązywania połączeń Tylko jedna strona gniazda (lokalna) ma przypisany adres: 156.17.4.30:80 *:* TCP: gniazda połączone Tworzone dla klienta i serwera po połączeniu, do wymiany właściwych danych. Gniazdo serwera: 156.17.4.30:80 22.33.44.55:44444 Gniazdo klienta: 22.33.44.55:44444 156.17.4.30:80 TCP: gniazdo opisywane przez cztery elementy: lokalny IP, lokalny port, zdalny IP, zdalny port. demonstracja "13
Dobrze znane porty Skąd wiemy, że powinniśmy się łączyć właśnie z portem 22? Dobrze znane porty (well known ports) Niektóre usługi mają porty zarezerwowane przez standardy: 22 - port SSH 80 - port HTTP 443 - port HTTPS /etc/services "14
Przykład klienta i serwera TCP server1.c + telnet server1.c + client1.c demonstracja "15
Obrazek z książki Programowanie usług sieciowych, R. Stevens
Implementacja TCP
Flagi w segmencie TCP 0 7 8 15 16 23 24 31 port źródłowy port docelowy numer sekwencyjny (numer pierwszego bajtu w segmencie) numer ostatniego potwierdzanego bajtu + 1 offset 000 ECN U-A-P-R-S-F oferowane okno suma kontrolna wskaźnik pilnych danych dodatkowe opcje, np. potwierdzanie selektywne Flagi = zapalone bity SYN = synchronize (do nawiązywania połączenia) ACK = pole numer potwierdzanego bajtu ma znaczenie FIN = finish (do kończenia połączenia) "18
Cykl życia połączenia Trójfazowe nawiązywanie połączenia Przesyłanie danych. Czterofazowe kończenie połączenia "19
Trójfazowe nawiązywanie połączenia (1) klient TCP serwer TCP CLOSED CLOSED socket() socket(), bind(), listen() SYN SENT connect() blokuje SYN 0 accept() blokuje LISTEN ACK 1, SYN 0 SYN RECEIVED ESTABLISHED connect() powraca ACK 1 accept() powraca ESTABLISHED "20
Trójfazowe nawiązywanie połączenia (2) Przejście do stanu LISTEN = otwarcie bierne (nie wysyła pakietu), wykonuje serwer TCP. Przejście do stanu SYN_SENT = otwarcie czynne (wysyła segment SYN), wykonuje klient TCP. W rzeczywistości w segmencie SYN nie jest wysyłany numer 0, tylko początkowy numer sekwencyjny: losowy, trudny do zgadnięcia zapobiega podszywaniu się! łatwo sfałszować źródłowy adres IP, ale trudno z takiego adresu rozpocząć komunikację TCP. "21
Czterofazowe kończenie połączenia (1) ESTABLISHED ESTABLISHED FIN WAIT 1 close() FIN M FIN WAIT 2 ACK M+1 CLOSE WAIT Od tej pory wywołania recv() zwrócą 0. "22
Czterofazowe kończenie połączenia (2) ESTABLISHED ESTABLISHED FIN WAIT 1 close() FIN M ACK M+1 CLOSE WAIT FIN WAIT 2 FIN N close() LAST ACK TIME WAIT ACK N+1 ~ 1 min. CLOSED CLOSED "23
Po co jest stan TIME WAIT? FIN WAIT 2 FIN N close() LAST ACK TIME WAIT ACK N+1 ~ 1 min. CLOSED CLOSED wie, że połączenie zostało zakończone Lewa strona nie wie, czy prawa strona dostała jej ACK Końcowy ACK nie dociera prawa strona wysyła FIN jeszcze raz lewa strona chce go poprawnie obsłużyć. Dodatkowy cel: nie chcemy żeby ktoś szybko utworzył połączenie TCP o takich samych parametrach (IP + porty) stare duplikaty segmentów mogłyby być uznane za należące do nowego połączenia. "24
Stany TCP: sytuacje nietypowe Obrazek ze strony https://en.wikipedia.org/wiki/transmission_control_protocol "25
Segment RST Segment z flagą RST (reset): wysyłany kiedy wystąpi błąd. Przykładowo w odpowiedzi na dowolny segment wysłany do zamkniętego portu. Po otrzymaniu takiego segmentu z gniazda nie można już korzystać. "26
Wysyłanie większych danych
Funkcja send() Czy nasz klient działa poprawnie? Wyślijmy 1, 2, 3,... mln bajtów "28
Funkcja send() Czy nasz klient działa poprawnie? Wyślijmy 1, 2, 3,... mln bajtów send() może wysłać mniej i to nie jest błąd! "28
Funkcja send() Czy nasz klient działa poprawnie? Wyślijmy 1, 2, 3,... mln bajtów send() może wysłać mniej i to nie jest błąd! send() zapisuje dane tylko do bufora wysyłkowego, zakończenie tej funkcji nie oznacza faktycznego wysłania. "28
Funkcja recv() Nowa wersja klienta server1.c + client2.c Wyślijmy 1, 2, 3,... mln bajtów. Klient zostaje zabity przez SIGPIPE?! "29
Dlaczego SIGPIPE? "30
Dlaczego SIGPIPE? klient: send() zapisuje część lub całość do bufora wysyłkowego. "30
Dlaczego SIGPIPE? klient: send() zapisuje część lub całość do bufora wysyłkowego. TCP po stronie klienta wysyła kilka segmentów (np. po 16 KB). "30
Dlaczego SIGPIPE? klient: send() zapisuje część lub całość do bufora wysyłkowego. TCP po stronie klienta wysyła kilka segmentów (np. po 16 KB). TCP po stronie serwera potwierdza segmenty (część lub wszystkie) "30
Dlaczego SIGPIPE? klient: send() zapisuje część lub całość do bufora wysyłkowego. TCP po stronie klienta wysyła kilka segmentów (np. po 16 KB). TCP po stronie serwera potwierdza segmenty (część lub wszystkie) serwer: recv() odczytuje pewien fragment bufora odbiorczego (np. 32 KB) "30
Dlaczego SIGPIPE? klient: send() zapisuje część lub całość do bufora wysyłkowego. TCP po stronie klienta wysyła kilka segmentów (np. po 16 KB). TCP po stronie serwera potwierdza segmenty (część lub wszystkie) serwer: recv() odczytuje pewien fragment bufora odbiorczego (np. 32 KB) serwer: wysyła odpowiedź i zamyka połączenie. "30
Dlaczego SIGPIPE? klient: send() zapisuje część lub całość do bufora wysyłkowego. TCP po stronie klienta wysyła kilka segmentów (np. po 16 KB). TCP po stronie serwera potwierdza segmenty (część lub wszystkie) serwer: recv() odczytuje pewien fragment bufora odbiorczego (np. 32 KB) serwer: wysyła odpowiedź i zamyka połączenie. klient: send() wysyła kolejny segment. "30
Dlaczego SIGPIPE? klient: send() zapisuje część lub całość do bufora wysyłkowego. TCP po stronie klienta wysyła kilka segmentów (np. po 16 KB). TCP po stronie serwera potwierdza segmenty (część lub wszystkie) serwer: recv() odczytuje pewien fragment bufora odbiorczego (np. 32 KB) serwer: wysyła odpowiedź i zamyka połączenie. klient: send() wysyła kolejny segment. TCP po stronie serwera odpowiada segmentem RST. "30
Dlaczego SIGPIPE? klient: send() zapisuje część lub całość do bufora wysyłkowego. TCP po stronie klienta wysyła kilka segmentów (np. po 16 KB). TCP po stronie serwera potwierdza segmenty (część lub wszystkie) serwer: recv() odczytuje pewien fragment bufora odbiorczego (np. 32 KB) serwer: wysyła odpowiedź i zamyka połączenie. klient: send() wysyła kolejny segment. TCP po stronie serwera odpowiada segmentem RST. TCP po stronie klienta zamyka gniazdo. "30
Dlaczego SIGPIPE? klient: send() zapisuje część lub całość do bufora wysyłkowego. TCP po stronie klienta wysyła kilka segmentów (np. po 16 KB). TCP po stronie serwera potwierdza segmenty (część lub wszystkie) serwer: recv() odczytuje pewien fragment bufora odbiorczego (np. 32 KB) serwer: wysyła odpowiedź i zamyka połączenie. klient: send() wysyła kolejny segment. TCP po stronie serwera odpowiada segmentem RST. TCP po stronie klienta zamyka gniazdo. klient: send() wysyła kolejny segment (przez zamknięte gniazdo!) otrzymuje sygnał SIGPIPE. "30
Na czym polega problem? Do jakiego momentu recv() powinno czytać dane? Problem: nie zdefiniowaliśmy protokołu komunikacji! Podejście nr 1: ustalamy znacznik końca rekordu (np. koniec wiersza) i czytamy do tego znacznika. Podejście nr 2: na początku wysyłamy rozmiar danych. "31
Tryb nieblokujący Funkcja recv(): Standardowe wywołanie blokuje aż w gnieździe będą jakiekolwiek dane. Może być tylko jeden bajt! Zazwyczaj nie chcemy czekać więcej niż x sekund. Tryb nieblokujący: Czwarty parametr recv() równy MSG_DONTWAIT. Jeśli w gnieździe nie ma pakietów, to recv() kończy działanie zwracając -1 zaś errno = EWOULDBLOCK. "32
Tryb nieblokujący Funkcja recv(): Standardowe wywołanie blokuje aż w gnieździe będą jakiekolwiek dane. Może być tylko jeden bajt! Zazwyczaj nie chcemy czekać więcej niż x sekund. Tryb nieblokujący: Czwarty parametr recv() równy MSG_DONTWAIT. Jeśli w gnieździe nie ma pakietów, to recv() kończy działanie zwracając -1 zaś errno = EWOULDBLOCK. Aktywne czekanie: Wywołujemy w pętli cały czas recv(sockfd,_,_,msg_dontwait). Sprawdzamy, ile czasu upłynęło od ostatniego odczytu. Wada: 100% zużycie procesora! "32
Przypomnienie: funkcja select() Czekanie maksymalnie x sekund na dane w gnieździe sockfd. fd_set descriptors; FD_ZERO (&descriptors); FD_SET (sockfd, &descriptors); struct timeval tv; tv.tv_sec = x; tv.tv_usec = 0; int ready = select (sockfd+1, &descriptors, NULL, NULL, &tv); ready < 0 wystąpił błąd. ready = 0 nastąpił timeout (po x sekundach). ready > 0 ready obserwowanych deskryptorów gotowych do odczytu. Pierwsze wywołanie recv(sockfd, ) nie zablokuje. W gnieździe sockfd może być więcej danych można je odczytać w trybie nieblokującym. "33
Lektura dodatkowa Kurose, Ross: rozdział 3 Tanenbaum: rozdział 6 Stevens: rozdziały 3-6, 13, 27 Beej's Guide to Network Programmin: https://beej.us/guide/bgnet/ "34