9.13. TCP sokety¶
TCP sokety se vyskytují ve dvou podobách, které vypadají odlišně, ale sdílejí stejný základní typ: klientské sokety, které se pomocí connect() připojí ke vzdálenému serveru, a serverové sokety, které pomocí bind(), listen() a accept() přijímají příchozí spojení. Obě role používají stejnou třídu socket představenou na stránce Objekty socketů; liší se pouze metody, které se na nich volají.
9.13.1. TCP klient¶
Nejjednodušší klient otevře spojení, odešle požadavek, přečte odpověď a zavře:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.1.20", 9000))
s.send(b"hello\n")
reply = s.recv(1024)
print("reply:", reply)
s.close()
connect() provede třícestný handshake popsaný na TCP – spolehlivý proud bajtů a vrátí řízení, jakmile je spojení otevřené. send() zapíše bajty do spojení; recv() z něj přečte až daný počet bajtů. Jakmile aplikace skončí, close() spojení ukončí.
Stejný skript zabalený do idiomu příkazu with ze stránky Objekty socketů, takže se soket zavře i v případě, že něco vyvolá výjimku:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("192.168.1.20", 9000))
s.send(b"hello\n")
print(s.recv(1024))
9.13.1.1. Čtení až do konce¶
Jedno volání recv() vrátí až požadovaný počet bajtů – může jich vrátit méně, protože TCP je proud (stream), nikoli posloupnost zpráv. Aplikace musí číst opakovaně, dokud nemá celou odpověď:
chunks = []
while True:
chunk = s.recv(1024)
if not chunk: # empty bytes -> other side closed
break
chunks.append(chunk)
reply = b"".join(chunks)
Smyčka skončí, když recv() vrátí prázdné bytes. K tomu dojde, když druhá strana čistě uzavřela svou polovinu spojení; aplikace v tomto stylu protokolu čte „konec proudu“ stejně jako „konec zprávy“.
9.13.1.2. Odesílání až do konce¶
Opačná výhrada platí pro send(): může odeslat méně bajtů, než bylo požadováno, a vrátí počet skutečně zapsaných bajtů. U velkých dat opakujte odeslání neodeslaného zbytku:
payload = some_big_bytes
while payload:
n = s.send(payload)
payload = payload[n:]
sendall() provádí tuto smyčku interně, takže většina kódu může jednoduše zavolat tuto metodu a vyhnout se ručnímu opakování:
s.sendall(some_big_bytes)
9.13.2. TCP server¶
Serverová strana má čtyři kroky: obsadit port, přepnout soket do režimu naslouchání, přijímat spojení jedno po druhém a komunikovat na každém přijatém soketu. Minimální echo server:
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("0.0.0.0", 9000))
server.listen(1)
print("listening on port 9000")
while True:
conn, addr = server.accept()
print("connection from", addr)
while True:
data = conn.recv(1024)
if not data:
break
conn.send(data) # echo back
conn.close()
Krok za krokem:
bind()obsadí hostitele a port na kameře."0.0.0.0"přijímá na libovolném rozhraní; jeho nahrazením konkrétní IP adresou se naslouchání omezí pouze na dané rozhraní.listen()přepne soket z normálního soketu na naslouchající soket. Argumentem je backlog – kolik čekajících spojení bude MicroPython řadit do fronty, zatímco je aplikace zaneprázdněna. Zvolte malé číslo;1je pro většinu případů dostačující.accept()blokuje, dokud se nepřipojí klient, a poté vrátí(conn, addr): nový soket reprezentující toto jedno spojení a adresu klienta. Samotný naslouchající soket zůstává otevřený, aby mohl přijímat další.Všechny bajty této konverzace procházejí přes
conn, nový soket. Čtení a zápisy používají stejná volánírecv()/send()jako na straně klienta.Když klient ukončí spojení,
recv()vrátíb""; vnitřní smyčka skončí a server zavře svou stranu pomocíclose().
Vnější while True se vrátí zpět k accept(), aby čekal na dalšího klienta. Server v této podobě obsluhuje jednoho klienta po druhém; provoz více klientů paralelně vyžaduje buď vlákna, nebo asyncio. To druhé je tématem následující stránky.
9.13.3. Časté chyby¶
Považování recv() za zprávově orientované. To není. Dvě volání
send(b"hi")mohou dorazit jako jednorecv(4)sb"hihi", nebo jako dvěrecv(2). Aplikace musí přidat rámcování, pokud na hranicích zpráv záleží – odřádkování, prefix délky, cokoli.Zapomenutí na opakování při krátkých odesláních. Pro cokoli nad několik stovek bajtů použijte
sendall().Zapomenutí zavřít přijatý soket. Každý
connje samostatný soket; zavření naslouchajícího soketu nezavře ty přijaté. Blokywithna obou to znesnadňují pokazit:while True: with server.accept()[0] as conn: # ... talk on conn ...
Opětovné navázání na port, který je stále ve stavu TIME_WAIT. Když se server restartuje během několika sekund od svého ukončení,
bind()může selhat s chybou „address in use“, protože MicroPython stále drží port z předchozího spojení.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)předbind()to vyřeší.
9.13.4. Co dál¶
Blokování na accept() znamená, že server může obsluhovat pouze jednoho klienta najednou. Blokování na recv() znamená, že jediný pomalý klient zablokuje celou smyčku. Standardním řešením na kameře je asyncio – spustit každé spojení jako vlastní úlohu a nechat smyčku událostí přepínat mezi nimi. Následující stránka popisuje asyncio verze všeho, co je na této stránce.