9.13. TCP-socketar¶
TCP-socketar finns i två former som ser olika ut men delar samma underliggande typ: klient-socketar som connect() till en fjärrserver, och server-socketar som bind(), listen() och accept() inkommande anslutningar. Båda rollerna använder samma klass socket som introducerades på Socket-objekt; bara metoderna som anropas på dem skiljer sig åt.
9.13.1. En TCP-klient¶
Den enklaste klienten öppnar en anslutning, skickar en förfrågan, läser svaret och stänger:
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() kör den trevägshandskakning som beskrivs på TCP – en pålitlig ström av byte och returnerar när anslutningen är öppen. send() skriver byte till anslutningen; recv() läser upp till ett givet antal byte från den. När programmet är klart stänger close() ner anslutningen.
Samma skript inkapslat i with-satsens idiom från Socket-objekt, så att socketen stängs även om något utlöser ett undantag:
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. Läsa tills allt är klart¶
Ett enskilt recv() returnerar upp till det begärda antalet byte – det kan returnera färre, eftersom TCP är en ström snarare än en sekvens av meddelanden. Programmet måste fortsätta läsa tills det har hela svaret:
chunks = []
while True:
chunk = s.recv(1024)
if not chunk: # empty bytes -> other side closed
break
chunks.append(chunk)
reply = b"".join(chunks)
Slingan slutar när recv() returnerar ett tomt bytes. Det händer när motparten rent har stängt sin halva av anslutningen; programmet läser ”slut på ström” som detsamma som ”slut på meddelande” i denna typ av protokoll.
9.13.1.2. Skicka tills allt är klart¶
Den motsatta reservationen gäller för send(): den kan skicka färre byte än begärt och returnerar antalet byte som faktiskt skrivits. För stora nyttolaster, gör ett nytt försök med den oskickade resten:
payload = some_big_bytes
while payload:
n = s.send(payload)
payload = payload[n:]
sendall() gör slingan internt, så det mesta av koden kan helt enkelt anropa den och undvika det manuella återförsöket:
s.sendall(some_big_bytes)
9.13.2. En TCP-server¶
Serversidan består av fyra steg: gör anspråk på en port, växla socketen till lyssningsläge, acceptera anslutningar en i taget, kommunicera på varje accepterad socket. En minimal ekoserver:
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()
Steg för steg:
bind()gör anspråk på en värd och en port på kameran."0.0.0.0"accepterar på vilket gränssnitt som helst; om du ersätter det med en specifik IP begränsas lyssnaren till det gränssnittet.listen()växlar socketen från en vanlig socket till en lyssnande socket. Argumentet är backlog – hur många väntande anslutningar MicroPython kommer att köa medan programmet är upptaget. Välj ett litet tal;1räcker i de flesta fall.accept()blockerar tills en klient ansluter och returnerar sedan(conn, addr): en ny socket som representerar denna enda anslutning, och klientens adress. Den lyssnande socketen själv förblir öppen för att acceptera fler.Alla byte för konversationen flödar genom
conn, den nya socketen. Läsningar och skrivningar använder samma anroprecv()/send()som på klientsidan.När klienten stänger returnerar
recv()b""; den inre slingan slutar och servern stänger sin ände medclose().
Den yttre while True hoppar tillbaka till accept() för att vänta på nästa klient. Servern hanterar en klient åt gången i denna form; att köra flera klienter parallellt kräver antingen trådar eller asyncio. Det senare är ämnet för nästa sida.
9.13.3. Vanliga fallgropar¶
Att behandla recv() som meddelandeformat. Det är det inte. Två anrop till
send(b"hi")kan komma in som ettrecv(4)avb"hihi", eller som tvårecv(2). Programmet måste lägga till ramning om meddelandegränser har betydelse – en radbrytning, ett längdprefix, vad som helst.Att glömma att göra nya försök vid korta sändningar. Använd
sendall()för allt utöver några hundra byte.Att glömma att stänga den accepterade socketen. Varje
connär en separat socket; att stänga den lyssnande socketen stänger inte de accepterade.with-block på båda gör detta svårt att göra fel:while True: with server.accept()[0] as conn: # ... talk on conn ...
Att binda om till en port som fortfarande är i TIME_WAIT. När en server startas om inom några sekunder efter att den stängts kan
bind()misslyckas med ”address in use” eftersom MicroPython fortfarande håller porten för den föregående anslutningen.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)förebind()rensar detta.
9.13.4. Vad händer härnäst¶
Att blockera på accept() innebär att servern bara kan betjäna en klient åt gången. Att blockera på recv() innebär att en enda långsam klient låser hela slingan. Standardsvaret på kameran är asyncio – kör varje anslutning som sin egen uppgift och låt händelseslingan växla mellan dem. Nästa sida täcker asyncio-versionerna av allt på denna sida.