Sieci · TCP i UDP

TCP i UDP — dobieranie protokołu do zastosowania

Gniazda TCP i UDP w Pythonie na localhost: połączenie, datagram, strumień kontra granice wiadomości i zamknięty port. Trzy zadania.

45 minutPoziom podstawowyPliki i rozwiązania

Materiał redakcyjny TablicaPro · liceum i technikum

klient.sendall(b"ab")
klient.sendall(b"cd")
# odbiorca moze odczytac: b"abcd"
TCP odebrane razem: abcd
UDP datagramy: ab cd

Dwa protokoły warstwy transportowej obsługują prawie cały internet: TCP dba o to, by dane dotarły w całości i w kolejności, a UDP wysyła pojedyncze datagramy bez gwarancji. Wybór zależy od zastosowania: strona WWW i poczta potrzebują TCP, rozmowa wideo i gry w czasie rzeczywistym wolą szybkość UDP. Na lekcji uczniowie sami piszą krótkie programy sieciowe na własnym komputerze (adres 127.0.0.1) i widzą różnice w działaniu.

Scenariusz jest przeznaczony na 45 minut dla klasy znającej funkcje w Pythonie, adresy IP i porty — na przykład po lekcji o adresach prywatnych i publicznych. Program łączy się tylko z komputerem, na którym działa; nie wysyła niczego do internetu.

Przygotowanie stanowisk

Potrzebny jest Python 3 i edytor lub IDLE. Rozpakuj pakiet z sekcji materiałów do osobnego folderu i uruchamiaj skrypty z tego folderu. Program uruchomisz przez Run → Run Module albo poleceniem python start.py, python3 start.py lub py start.py.

Cele i plan lekcji

Po zajęciach uczeń potrafi:

  • otworzyć połączenie TCP (connect, accept) i wymienić dane;
  • wysłać datagram UDP bez nawiązywania połączenia;
  • wyjaśnić, że TCP przesyła strumień bajtów, a UDP zachowuje granice wiadomości;
  • dobrać protokół do zastosowania i opisać zachowanie obu przy zamkniętym porcie.
CzasPraca na lekcji
0–5 minProblem: dlaczego film i poczta używają różnych protokołów?
5–15 minTCP: połączenie, potwierdzenia, kolejność
15–22 minUDP: datagramy bez połączenia
22–37 minTrzy zadania w Pythonie
37–42 minDobór protokołu do zastosowań
42–45 minPytania kontrolne

Porównanie

CechaTCPUDP
połączenienawiązywane przed wymianą danych (uzgodnienie trójetapowe)brak
niezawodnośćpotwierdzenia, ponowne wysyłanie, kolejnośćbrak gwarancji, datagramy mogą zginąć lub zamienić się kolejnością
jednostka danychstrumień bajtów (granice porcji nie są zachowane)datagram (granice zachowane)
narzut i opóźnieniewiększemałe
typowe zastosowaniaWWW (HTTP/HTTPS), poczta, przesyłanie plików, SSHDNS, strumieniowanie dźwięku i obrazu, gry sieciowe, VoIP

Gniazda w Pythonie

socket.socket(AF_INET, SOCK_STREAM) tworzy gniazdo TCP, a SOCK_DGRAM — UDP. Serwer TCP wywołuje bind (przypisanie adresu i portu), listen i accept (czekanie na klienta); klient łączy się metodą connect. Port 0 oznacza „wybierz wolny port” — prawdziwy numer odczytujemy z getsockname(). Gniazdo UDP używa sendto i recvfrom; adres odbiorcy podaje się przy każdym wysłaniu.

Wnioski z zadania 3: dwie porcje wysłane przez TCP (ab, cd) odbiorca może odczytać razem jako abcd — protokół nie zna granic wiadomości, dlatego aplikacje dodają własne (np. długość lub znak końca). Dwa datagramy UDP trafiają do odbiorcy jako dwie oddzielne wiadomości. Próba połączenia TCP z portem, na którym nikt nie nasłuchuje, kończy się natychmiast odrzuceniem, a wysłanie datagramu UDP na taki port „udaje się” — nadawca nie dostaje żadnego potwierdzenia.

Znane usługi mają przypisane numery portów: HTTP — 80, HTTPS — 443, DNS (domain) — 53 (UDP, a przy dużych odpowiedziach także TCP).

Zadania

Zadanie 1. TCP. Uruchom serwer TCP na 127.0.0.1 (port 0), który odsyła dane wielkimi literami. Połącz się klientem, wyślij witaj i wypisz odpowiedź.

Zadanie 2. UDP. Uruchom serwer UDP odsyłający dane od tyłu. Wyślij datagram witaj bez nawiązywania połączenia i wypisz odpowiedź.

Zadanie 3. Różnice. (a) Wyślij przez TCP dwie porcje ab i cd i odczytaj dane odbiorcą. (b) Wyślij dwa datagramy UDP i odbierz je osobno. (c) Spróbuj połączyć się przez TCP i wysłać datagram UDP na zamknięty port.

Dla chętnych: sprawdź numery portów usług HTTP, HTTPS i DNS funkcją socket.getservbyname.

Rozwiązanie i wyniki

import socket
import threading


def serwer_tcp(gniazdo):
    polaczenie, _ = gniazdo.accept()
    with polaczenie:
        dane = polaczenie.recv(1024)
        polaczenie.sendall(dane.upper())


def serwer_udp(gniazdo):
    dane, adres = gniazdo.recvfrom(1024)
    gniazdo.sendto(dane[::-1], adres)


# Zadanie 1 - TCP: polaczenie, wymiana danych, zamkniecie
serwer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serwer.bind(("127.0.0.1", 0))
serwer.listen(1)
port_tcp = serwer.getsockname()[1]
threading.Thread(target=serwer_tcp, args=(serwer,), daemon=True).start()
klient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
klient.connect(("127.0.0.1", port_tcp))
klient.sendall(b"witaj")
print("TCP odpowiedz:", klient.recv(1024).decode())
klient.close()
serwer.close()

# Zadanie 2 - UDP: datagram bez nawiazywania polaczenia
serwer = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serwer.bind(("127.0.0.1", 0))
port_udp = serwer.getsockname()[1]
threading.Thread(target=serwer_udp, args=(serwer,), daemon=True).start()
klient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
klient.sendto(b"witaj", ("127.0.0.1", port_udp))
odpowiedz, _ = klient.recvfrom(1024)
print("UDP odpowiedz:", odpowiedz.decode())
klient.close()
serwer.close()

# Zadanie 3 - roznice
# a) TCP to strumien bajtow: dwie porcje moga zostac odczytane razem
serwer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serwer.bind(("127.0.0.1", 0))
serwer.listen(1)
port = serwer.getsockname()[1]
klient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
klient.connect(("127.0.0.1", port))
polaczenie, _ = serwer.accept()
klient.sendall(b"ab")
klient.sendall(b"cd")
odebrane = b""
while len(odebrane) < 4:
    odebrane += polaczenie.recv(4 - len(odebrane))
print("TCP odebrane razem:", odebrane.decode())
klient.close()
polaczenie.close()
serwer.close()

# b) UDP zachowuje granice datagramow
odbiorca = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
odbiorca.bind(("127.0.0.1", 0))
nadawca = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
nadawca.sendto(b"ab", odbiorca.getsockname())
nadawca.sendto(b"cd", odbiorca.getsockname())
print("UDP datagramy:", odbiorca.recvfrom(1024)[0].decode(), odbiorca.recvfrom(1024)[0].decode())
nadawca.close()
odbiorca.close()

# c) polaczenie z zamknietym portem
zamkniety = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
zamkniety.bind(("127.0.0.1", 0))
port_zamkniety = zamkniety.getsockname()[1]
zamkniety.close()
proba = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    proba.connect(("127.0.0.1", port_zamkniety))
    print("TCP: polaczono")
except ConnectionRefusedError:
    print("TCP: polaczenie odrzucone")
proba.close()
wysylajacy = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
wysylajacy.sendto(b"x", ("127.0.0.1", port_zamkniety))
print("UDP: wyslano bez bledu")
wysylajacy.close()

Program wypisze:

TCP odpowiedz: WITAJ
UDP odpowiedz: jatiw
TCP odebrane razem: abcd
UDP datagramy: ab cd
TCP: polaczenie odrzucone
UDP: wyslano bez bledu

Odpowiedź jatiw to napis witaj odczytany od tyłu. Zadanie dla chętnych (rozszerzenie.py) wypisuje:

80 443 53

Numer portu serwera jest za każdym razem inny (wybiera go system), więc nie pojawia się w wynikach.

Typowe błędy

ObjawCo sprawdzić
ConnectionRefusedErrorNikt nie nasłuchuje na podanym porcie albo serwer jeszcze się nie uruchomił; uruchom serwer przed klientem.
Program „wisi” na recv lub acceptDruga strona nic nie wysłała; ustaw limit czasu (settimeout) lub sprawdź kolejność wywołań.
OSError: address already in usePort jest zajęty; użyj portu 0 albo zamknij poprzedni program.
Dane TCP odczytane „skleiły się”To normalne: TCP to strumień bajtów; dodaj własny separator lub długość wiadomości.
Datagram UDP zaginąłUDP nie gwarantuje dostarczenia; w sieci lokalnej zdarza się rzadko, w internecie — regularnie.
Zapora blokuje połączeniePierwsze uruchomienie serwera może wywołać pytanie zapory systemu; połączenia na 127.0.0.1 nie opuszczają komputera.

Pytania kontrolne i punktacja

  1. Który protokół wybierzesz do rozmowy wideo w czasie rzeczywistym i dlaczego?
  2. Co oznacza, że TCP przesyła „strumień bajtów”?
  3. Jaki numer portu ma HTTPS?

Odpowiedzi: UDP — ważniejsza jest mała zwłoka niż odzyskanie każdego zagubionego fragmentu. Że odbiorca nie widzi granic między wysłanymi porcjami; mogą zostać odczytane razem. 443.

Proponowana punktacja (razem 10 punktów): zadanie 1 — serwer TCP 1 pkt, klient i odpowiedź 2 pkt; zadanie 2 — serwer UDP 1 pkt, klient i odpowiedź 1 pkt; zadanie 3 — dwie porcje TCP 1 pkt, dwa datagramy UDP 1 pkt, zamknięty port 1 pkt; dobór protokołu w pytaniach 1 pkt. Zadanie dla chętnych nie jest wymagane do pełnej oceny.

Materiały do pobrania

Rozpakuj ZIP przed otwarciem plików. Materiały są dostępne bez logowania; publiczne rozwiązanie służy nauce i samodzielnemu sprawdzeniu, a nie poufnemu sprawdzianowi.

Co dalej?

Następny krok to model OSI i enkapsulacja danych oraz analiza ruchu narzędziami typu Wireshark. Ten materiał rozwija dział o warstwie transportowej w kursie Sieci w TablicaPro, gdzie znajdziesz prezentacje, ćwiczenia i testy do prowadzenia lekcji.

Źródła techniczne: moduł socket w dokumentacji Pythona oraz TCP i UDP w Wikipedii. Zobacz też lekcję o adresach prywatnych.

Poprowadź kolejne lekcje Sieci

Pełny kurs: prezentacje, ćwiczenia i testy do pracy z klasą.

Kup dostęp do kursu Sieci →

Zobacz program kursu