9.13. TCP-socketit¶
TCP-socketeja on kahta muotoa, jotka näyttävät erilaisilta mutta jakavat saman taustalla olevan tyypin: asiakas-socketit, jotka käyttävät connect() -metodia muodostaakseen yhteyden etäpalvelimeen, ja palvelin-socketit, jotka käyttävät bind()-, listen()- ja accept() -metodeja saapuville yhteyksille. Molemmat roolit käyttävät samaa socket -luokkaa, joka esiteltiin sivulla Socket-objektit; vain niillä kutsuttavat metodit eroavat.
9.13.1. TCP-asiakas¶
Yksinkertaisin asiakas avaa yhteyden, lähettää pyynnön, lukee vastauksen ja sulkee yhteyden:
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() suorittaa kolmivaiheisen kättelyn, joka käsiteltiin sivulla TCP – luotettava tavuvirta, ja palaa, kun yhteys on auki. send() kirjoittaa tavuja yhteyteen; recv() lukee yhteydestä enintään annetun määrän tavuja. Kun sovellus on valmis, close() sulkee yhteyden.
Sama skripti with -lauseen idiomilla käärittynä sivulta Socket-objektit, jolloin socket suljetaan, vaikka jokin nostaisi poikkeuksen:
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. Lukeminen loppuun asti¶
Yksittäinen recv() palauttaa enintään pyydetyn määrän tavuja – se voi palauttaa vähemmän, koska TCP on virta eikä viestien jono. Sovelluksen on jatkettava lukemista, kunnes se on saanut koko vastauksen:
chunks = []
while True:
chunk = s.recv(1024)
if not chunk: # empty bytes -> other side closed
break
chunks.append(chunk)
reply = b"".join(chunks)
Silmukka päättyy, kun recv() palauttaa tyhjän bytes -arvon. Näin käy, kun toinen osapuoli on sulkenut siististi oman puolensa yhteydestä; tämäntyyppisessä protokollassa sovellus tulkitsee ”virran lopun” samaksi kuin ”viestin loppu”.
9.13.1.2. Lähettäminen loppuun asti¶
Päinvastainen varaus koskee send() -metodia: se saattaa lähettää vähemmän tavuja kuin pyydettiin ja palauttaa todella kirjoitettujen tavujen määrän. Suurten hyötykuormien kohdalla lähetä uudelleen lähettämättä jäänyt loppuosa:
payload = some_big_bytes
while payload:
n = s.send(payload)
payload = payload[n:]
sendall() tekee silmukan sisäisesti, joten useimmat koodit voivat vain kutsua sitä ja välttää manuaalisen uudelleenyrityksen:
s.sendall(some_big_bytes)
9.13.2. TCP-palvelin¶
Palvelinpuoli on neljä vaihetta: varaa portti, vaihda socket kuuntelutilaan, hyväksy yhteydet yksi kerrallaan ja keskustele kullakin hyväksytyllä socketilla. Minimaalinen echo-palvelin:
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()
Vaihe vaiheelta:
bind()varaa kameralta isännän ja portin."0.0.0.0"hyväksyy yhteyksiä millä tahansa rajapinnalla; sen korvaaminen tietyllä IP-osoitteella rajoittaa kuuntelijan kyseiseen rajapintaan.listen()vaihtaa socketin tavallisesta socketista kuuntelevaksi socketiksi. Argumentti on backlog eli kuinka monta odottavaa yhteyttä MicroPython jonottaa sovelluksen ollessa varattuna. Valitse pieni luku;1riittää useimmissa tapauksissa.accept()estyy, kunnes asiakas muodostaa yhteyden, ja palauttaa sitten(conn, addr): uuden socketin, joka edustaa tätä yhtä yhteyttä, sekä asiakkaan osoitteen. Kuunteleva socket itse pysyy auki hyväksymään lisää yhteyksiä.Kaikki keskustelun tavut kulkevat
conn-socketin, eli uuden socketin, kautta. Lukeminen ja kirjoittaminen käyttävät samojarecv()/send()-kutsuja kuin asiakaspuolella.Kun asiakas sulkee yhteyden,
recv()palauttaab""; sisempi silmukka päättyy ja palvelin sulkee oman päänsäclose()-metodilla.
Ulompi while True hyppää takaisin accept() -metodiin odottamaan seuraavaa asiakasta. Palvelin käsittelee tässä muodossa yhtä asiakasta kerrallaan; useamman asiakkaan ajaminen rinnakkain vaatii joko säikeitä tai asyncio -moduulia. Jälkimmäinen on seuraavan sivun aihe.
9.13.3. Yleisiä sudenkuoppia¶
recv():n kohteleminen viestimuotoisena. Se ei ole sitä. Kaksi
send(b"hi")-kutsua saattavat saapua yhtenärecv(4)-kutsuna, joka tuottaab"hihi", tai kahtenarecv(2)-kutsuna. Sovelluksen on lisättävä kehystys, jos viestien rajat ovat tärkeitä – rivinvaihto, pituusetuliite, mikä tahansa.Lyhyiden lähetysten uudelleenyrityksen unohtaminen. Käytä
sendall()-metodia kaikkeen, joka ylittää muutaman sadan tavun.Hyväksytyn socketin sulkemisen unohtaminen. Jokainen
connon erillinen socket; kuuntelevan socketin sulkeminen ei sulje hyväksyttyjä socketteja.with-lohkot molemmilla tekevät tämän vaikeaksi tehdä väärin:while True: with server.accept()[0] as conn: # ... talk on conn ...
Uudelleensitominen porttiin, joka on yhä TIME_WAIT-tilassa. Kun palvelin käynnistyy uudelleen muutaman sekunnin sisällä sulkemisesta,
bind()saattaa epäonnistua virheellä ”address in use”, koska MicroPython pitää porttia yhä varattuna edelliselle yhteydelle.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)ennenbind()-kutsua poistaa tämän.
9.13.4. Mitä seuraavaksi¶
Estyminen accept() -metodissa tarkoittaa, että palvelin voi palvella vain yhtä asiakasta kerrallaan. Estyminen recv() -metodissa tarkoittaa, että yksi hidas asiakas jumittaa koko silmukan. Vakiovastaus kameralla on asyncio – aja jokainen yhteys omana tehtävänään ja anna tapahtumasilmukan jakaa suoritusvuoroa niiden välillä. Seuraava sivu käsittelee tällä sivulla esitellyn kaiken asyncio-versiot.