9.13. TCP soketleri¶
TCP soketleri, farklı görünen ancak aynı temel türü paylaşan iki biçimde gelir: uzak bir sunucuya connect() ile bağlanan istemci soketleri ve gelen bağlantıları bind(), listen() ve accept() eden sunucu soketleri. Her iki rol de Soket nesneleri sayfasında tanıtılan aynı socket sınıfını kullanır; yalnızca üzerlerinde çağrılan yöntemler farklılaşır.
9.13.1. Bir TCP istemcisi¶
En basit istemci bir bağlantı açar, bir istek gönderir, yanıtı okur ve kapatır:
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(), TCP – güvenilir bir bayt akışı sayfasında ele alınan üç yönlü el sıkışmayı çalıştırır ve bağlantı açıldığında geri döner. send() bağlantıya bayt yazar; recv() ondan belirli bir sayıya kadar bayt okur. Uygulama işini bitirdiğinde close() bağlantıyı kapatır.
Aynı betik, Soket nesneleri sayfasındaki with deyimi deyimiyle sarmalanmıştır; böylece bir şey istisna fırlatsa bile soket kapatılır:
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. Bitene kadar okuma¶
Tek bir recv() çağrısı, istenen bayt sayısına kadar döndürür – daha azını döndürebilir, çünkü TCP bir mesaj dizisi değil bir akıştır. Uygulamanın, tam yanıtı alana kadar okumaya devam etmesi gerekir:
chunks = []
while True:
chunk = s.recv(1024)
if not chunk: # empty bytes -> other side closed
break
chunks.append(chunk)
reply = b"".join(chunks)
Döngü, recv() boş bir bytes döndürdüğünde sona erer. Bu, karşı taraf kendi bağlantı yarısını düzgünce kapattığında gerçekleşir; uygulama bu tür protokollerde “akış sonu”nu “mesaj sonu” ile aynı şekilde okur.
9.13.1.2. Bitene kadar gönderme¶
Tersi uyarı send() için geçerlidir: istenenden daha az bayt gönderebilir ve gerçekten yazılan bayt sayısını döndürür. Büyük yükler için, gönderilmeyen kalanı yeniden deneyin:
payload = some_big_bytes
while payload:
n = s.send(payload)
payload = payload[n:]
sendall() döngüyü dahili olarak yapar, böylece çoğu kod yalnızca bunu çağırabilir ve manuel yeniden denemeyi atlayabilir:
s.sendall(some_big_bytes)
9.13.2. Bir TCP sunucusu¶
Sunucu tarafı dört adımdan oluşur: bir port talep et, soketi dinleme moduna geçir, bağlantıları tek tek kabul et, kabul edilen her sokette iletişim kur. Minimal bir yankı (echo) sunucusu:
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()
Adım adım:
bind()kamerada bir host ve port talep eder."0.0.0.0"herhangi bir arabirimde kabul eder; bunu belirli bir IP ile değiştirmek dinleyiciyi yalnızca o arabirimle sınırlar.listen()soketi normal bir soketten dinleyen bir sokete geçirir. Argüman backlog değeridir – uygulama meşgulken MicroPython’ın kuyruğa alacağı bekleyen bağlantı sayısı. Küçük bir sayı seçin; çoğu durumda1yeterlidir.accept()bir istemci bağlanana kadar engeller, ardından(conn, addr)döndürür: bu tek bağlantıyı temsil eden yeni bir soket ve istemcinin adresi. Dinleyen soketin kendisi daha fazlasını kabul etmek için açık kalır.Konuşmanın tüm baytları yeni soket olan
connüzerinden akar. Okumalar ve yazmalar, istemci tarafındaki ile aynırecv()/send()çağrılarını kullanır.İstemci kapattığında
recv()b""döndürür; iç döngü sona erer ve sunucuclose()ile kendi tarafını kapatır.
Dıştaki while True bir sonraki istemciyi beklemek için accept() çağrısına geri atlar. Sunucu bu biçimde aynı anda bir istemciyi ele alır; paralel olarak birden fazla istemci çalıştırmak ya iş parçacıkları ya da asyncio gerektirir. İkincisi bir sonraki sayfanın konusudur.
9.13.3. Sık karşılaşılan tuzaklar¶
recv()’i mesaj biçimli sanmak. Öyle değildir. İki
send(b"hi")çağrısı tek birrecv(4)ileb"hihi"olarak gelebilir veya ikirecv(2)olarak gelebilir. Mesaj sınırları önemliyse, uygulamanın çerçeveleme eklemesi gerekir – bir satır sonu, bir uzunluk öneki, her neyse.Kısa gönderimlerde yeniden denemeyi unutmak. Birkaç yüz bayttan büyük her şey için
sendall()kullanın.Kabul edilen soketi kapatmayı unutmak. Her
connayrı bir sokettir; dinleyen soketi kapatmak kabul edilen soketleri kapatmaz. Her ikisindekiwithblokları bunu yanlış yapmayı zorlaştırır:while True: with server.accept()[0] as conn: # ... talk on conn ...
Hâlâ TIME_WAIT durumundaki bir porta yeniden bağlanmak. Bir sunucu kapanmasından birkaç saniye sonra yeniden başladığında, MicroPython hâlâ önceki bağlantı için portu tuttuğundan
bind()“address in use” hatasıyla başarısız olabilir.bind()öncesindeserver.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)bunu temizler.
9.13.4. Sırada ne var¶
accept() üzerinde engellemek, sunucunun aynı anda yalnızca bir istemciye hizmet verebileceği anlamına gelir. recv() üzerinde engellemek, tek bir yavaş istemcinin tüm döngüyü askıya alması anlamına gelir. Kameradaki standart yanıt asyncio‘dur – her bağlantıyı kendi görevi olarak çalıştırın, olay döngüsünün aralarında görev dağıtmasına izin verin. Bir sonraki sayfa bu sayfadaki her şeyin asyncio sürümlerini ele alır.