7.1 Co to są sockety?
Kopmy głębiej. Najpierw nauczyliśmy się pracować z request
, potem z http.client
, a potem z proxy. Co dalej? A dalej zajrzymy wszystkim tym bibliotekom pod maskę…
Socket (dosłownie — gniazdko) — to punkt w sieci, przez który dane są wysyłane i odbierane. Socket można przedstawić jako końcowy punkt dwukierunkowego kanału komunikacji między dwoma programami, działającymi na jednej lub różnych maszynach.
Sockety obsługują różne protokoły sieciowe, ale najczęściej używane są dwa:
-
TCP
(Transmission Control Protocol): Niezawodny protokół, który zapewnia ustanowienie połączenia, sprawdzenie integralności danych i ich prawidłową kolejność. -
UDP
(User Datagram Protocol): Protokół, nie zorientowany na połączenie, który nie gwarantuje dostarczenia danych, ale jest szybszy i efektywniejszy dla niektórych aplikacji.
Do identyfikacji socketu używane są adres IP (identyfikujący urządzenie w sieci) i port (identyfikujący konkretne aplikacje lub usługę na urządzeniu).
Ważne!
Adres IP i port jednoznacznie identyfikują program w sieci. To jak adres domu i numer mieszkania. Adres domu (adres IP) — to adres twojego komputera w sieci, a port — to numer mieszkania, który program używa do otrzymywania i wysyłania wiadomości.
Więcej o tym, co to jest adres IP i port, możesz dowiedzieć się w wykładach, poświęconych budowie sieci.
Główne etapy pracy programu z socketami:
- Tworzenie socketu: Program tworzy socket, określając typ protokołu (TCP lub UDP).
- Powiązanie z adresem: Socket jest powiązany z adresem IP i numerem portu, aby być dostępnym dla połączeń lub wysyłania/odbierania danych.
- Nasłuchiwanie i ustanowienie połączenia (dla TCP):
- Nasłuchiwanie: Socket po stronie serwera zostaje przełączony w tryb nasłuchiwania, czekając na przychodzące połączenia.
- Ustanowienie połączenia: Klient inicjuje połączenie z serwerem. Serwer przyjmuje połączenie, tworząc nowy socket do komunikacji z klientem.
- Wymiana danych: Dane są przesyłane między klientem a serwerem. W przypadku TCP dane są przesyłane w niezawodnym porządku.
- Zamknięcie połączenia: Po zakończeniu wymiany danych połączenie się zamyka.
Zalety używania socketów:
- Elastyczność: Sockety pozwalają aplikacjom wymieniać dane niezależnie od ich lokalizacji i platformy.
- Wydajność: Sockety zapewniają szybki i efektywny sposób przesyłania danych, zwłaszcza w przypadku używania UDP.
- Niezawodność (w przypadku TCP): Protokół TCP zapewnia niezawodne dostarczanie danych ze sprawdzeniem integralności i odzyskaniem utraconych pakietów.
7.2 Tworzenie serwera socket
Praca z socketami w Pythonie odbywa się za pomocą wbudowanego modułu socket
, który zapewnia interfejs do niskopoziomowego programowania sieciowego.
Dzięki socketom można stworzyć socket-server
— aplikację/obiekt, który będzie otrzymywać żądania od klientów i odpowiadać im. A także socket-client
— aplikację/obiekt, który będzie wysyłać żądania do socket-serwera
i otrzymywać od niego odpowiedzi.
Aby stworzyć socket-server
, trzeba wykonać trzy czynności:
- Stworzyć obiekt socket-serwera.
- Powiązać (
bind
) go z jakimś IP i portem. - Włączyć tryb nasłuchiwania (
listen
) przychodzących wiadomości.
Tak będzie wyglądał ten kod:
import socket
# Tworzenie socketu
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Powiązanie socketu z adresem i portem
server_socket.bind(('localhost', 12345))
# Nasłuchiwanie przychodzących połączeń
server_socket.listen(5)
print("Serwer oczekuje na połączenie...")
Tutaj socket.AF_INET
oznacza, że używamy IPv4 dla protokołu sieciowego, a socket.SOCK_STREAM
oznacza, że używamy TCP. Te parametry są najczęściej używane do tworzenia aplikacji sieciowych.
Po przyjściu przychodzącej wiadomości, trzeba wykonać jeszcze cztery czynności:
- Nawiązać
(accept)
połączenie z klientem. - Odebrać
(receive)
żądanie (dane) od klienta. - Wysłać
(send)
klientowi odpowiedź — również jakieś dane. - Zamknąć
(close)
połączenie.
Tak będzie wyglądał ten kod:
# Akceptacja nowego połączenia
client_socket, client_address = server_socket.accept()
print(f"Połączenie nawiązane z {client_address}")
# Odbieranie danych od klienta
data = client_socket.recv(1024)
print(f"Otrzymano: {data.decode('utf-8')}")
# Wysyłanie danych do klienta
client_socket.sendall(b'Hello, client!')
# Zamknięcie połączenia z klientem
client_socket.close()
Metoda sendall()
jest używana zamiast send()
, ponieważ zapewnia, że wszystkie dane będą wysłane. Metoda send()
może wysłać tylko część danych, jeśli bufor się zapełni.
Liczba 1024 w recv(1024)
wskazuje maksymalną ilość bajtów, którą można otrzymać za jednym razem. To pomaga kontrolować ilość danych przetwarzanych w jednej operacji.
Kod z ostatniego przykładu zazwyczaj jest wykonywany w nieskończonej pętli — serwer przetwarza żądanie, potem czeka na nowe, potem je przetwarza, i tak bez przerwy.
Jeśli chcesz uruchomić go u siebie lub po prostu zobaczyć pełny przykład, to przedstawiam go tutaj:
import socket
# Tworzenie socketu
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Powiązanie socketu z adresem i portem
server_socket.bind(('localhost', 12345))
# Nasłuchiwanie przychodzących połączeń
server_socket.listen(5)
print("Serwer oczekuje na połączenie...")
while True:
# Akceptacja nowego połączenia
client_socket, client_address = server_socket.accept()
print(f"Połączenie nawiązane z {client_address}")
# Odbieranie danych od klienta
data = client_socket.recv(1024)
print(f"Otrzymano: {data.decode('utf-8')}")
# Wysyłanie danych do klienta
client_socket.sendall(b'Hello, client!')
# Zamknięcie połączenia z klientem
client_socket.close()
7.3 Tworzenie klienta socket
Socket-serwer stworzyliśmy, teraz napiszmy swojego socket-klienta, który będzie komunikował się z serwerem i odbierał od niego dane w odpowiedzi.
Aby to zrobić, trzeba wykonać pięć czynności:
- Stworzyć obiekt
socket-klienta
. - Nawiązać połączenie
(connect)
z adresem IP i portem naszegosocket-serwera
. - Wysłać
(send)
wiadomość na serwer. - Odebrać
(receive)
dane od serwera. - Zamknąć
(close)
połączenie.
To w rzeczywistości prostsze niż się wydaje. Tak będzie wyglądał ten kod:
import socket
# Tworzenie socketu
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Nawiązanie połączenia z serwerem
client_socket.connect(('localhost', 12345))
# Wysyłanie danych na serwer
client_socket.sendall(b'Hello, server!')
# Odbieranie danych od serwera
data = client_socket.recv(1024)
print(f"Otrzymano od serwera: {data.decode('utf-8')}")
# Zamknięcie socketu
client_socket.close()
Jeśli po drugiej stronie nie ma uruchomionego socket-serwera
lub połączenie zostało przerwane, to wystąpi wyjątek typu socket.error
. Więc nie zapomnij obsługiwać wyjątków.
Na tym dziś kończymy pracę z socketami.
Podczas każdej pracy z siecią znowu i znowu będą pojawiały się hosty, porty, adresy IP, ustanawianie połączeń, nasłuchiwanie żądań i tym podobne. Dlatego zrozumienie tego, jak to działa głęboko wewnątrz, bardzo ułatwi ci życie i pomoże połączyć rozproszone wiedzę w jedną całość.
GO TO FULL VERSION