9.13. TCP sockets¶
TCP socket hadir dalam dua bentuk yang tampak berbeda namun berbagi tipe dasar yang sama: socket klien yang connect() ke server jarak jauh, dan socket server yang bind(), listen(), serta accept() koneksi yang masuk. Kedua peran menggunakan kelas socket yang sama yang diperkenalkan pada Objek socket; hanya metode yang dipanggil pada keduanya yang berbeda.
9.13.1. TCP klien¶
Klien paling sederhana membuka koneksi, mengirim permintaan, membaca balasan, dan menutup:
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() menjalankan three-way handshake yang dibahas pada TCP -- aliran byte yang andal dan kembali ketika koneksi sudah terbuka. send() menulis byte ke koneksi; recv() membaca hingga sejumlah byte darinya. Setelah aplikasi selesai, close() menutup koneksi.
Skrip yang sama dibungkus dalam idiom pernyataan with dari Objek socket, sehingga socket ditutup meskipun ada sesuatu yang menyebabkan pengecualian:
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. Membaca hingga selesai¶
Satu recv() mengembalikan hingga jumlah byte yang diminta -- bisa mengembalikan lebih sedikit, karena TCP adalah stream bukan urutan pesan. Aplikasi harus terus membaca hingga mendapatkan balasan lengkap:
chunks = []
while True:
chunk = s.recv(1024)
if not chunk: # empty bytes -> other side closed
break
chunks.append(chunk)
reply = b"".join(chunks)
Loop berakhir ketika recv() mengembalikan bytes kosong. Hal itu terjadi ketika sisi lain telah menutup setengah koneksinya dengan bersih; aplikasi membaca "akhir stream" sebagai sama dengan "akhir pesan" dalam gaya protokol ini.
9.13.1.2. Mengirim hingga selesai¶
Peringatan sebaliknya berlaku untuk send(): mungkin mengirim lebih sedikit byte dari yang diminta, mengembalikan jumlah byte yang sebenarnya ditulis. Untuk payload besar, coba ulang sisa yang belum terkirim:
payload = some_big_bytes
while payload:
n = s.send(payload)
payload = payload[n:]
sendall() melakukan loop secara internal, sehingga sebagian besar kode cukup memanggil itu dan menghindari percobaan ulang manual:
s.sendall(some_big_bytes)
9.13.2. TCP server¶
Sisi server terdiri dari empat langkah: klaim port, alihkan socket ke mode mendengarkan, terima koneksi satu per satu, berkomunikasi pada setiap socket yang diterima. Server echo minimal:
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()
Langkah demi langkah:
bind()mengklaim host dan port pada kamera."0.0.0.0"menerima pada antarmuka apa pun; menggantinya dengan IP tertentu membatasi pendengar ke antarmuka tersebut.listen()mengalihkan socket dari socket normal ke socket mendengarkan. Argumennya adalah backlog -- berapa banyak koneksi yang tertunda yang akan diantrekan MicroPython saat aplikasi sibuk. Pilih angka kecil;1cukup untuk sebagian besar kasus.accept()memblokir hingga klien terhubung, lalu mengembalikan(conn, addr): socket baru yang mewakili satu koneksi ini, dan alamat klien. Socket mendengarkan itu sendiri tetap terbuka untuk menerima lebih banyak.Semua byte untuk percakapan mengalir melalui
conn, socket baru. Pembacaan dan penulisan menggunakan panggilanrecv()/send()yang sama seperti pada sisi klien.Ketika klien menutup,
recv()mengembalikanb""; loop dalam berakhir dan server menutup ujungnya denganclose().
while True luar melompat kembali ke accept() untuk menunggu klien berikutnya. Server menangani satu klien pada satu waktu dalam bentuk ini; menjalankan beberapa klien secara paralel membutuhkan thread atau asyncio. Yang terakhir adalah subjek halaman berikutnya.
9.13.3. Jebakan umum¶
Memperlakukan recv() sebagai berbentuk pesan. Tidak demikian. Dua panggilan
send(b"hi")mungkin tiba sebagai saturecv(4)darib"hihi", atau sebagai duarecv(2)s. Aplikasi harus menambahkan framing jika batas pesan penting -- baris baru, awalan panjang, apa pun.Lupa mencoba ulang pada pengiriman pendek. Gunakan
sendall()untuk apa pun yang lebih dari beberapa ratus byte.Lupa menutup socket yang diterima. Setiap
connadalah socket terpisah; menutup socket mendengarkan tidak menutup yang diterima. Blokwithpada keduanya membuat hal ini sulit untuk salah:while True: with server.accept()[0] as conn: # ... talk on conn ...
Mengikat ulang ke port yang masih dalam TIME_WAIT. Ketika server dimulai ulang dalam beberapa detik setelah ditutup,
bind()mungkin gagal dengan "address in use" karena MicroPython masih memegang port untuk koneksi sebelumnya.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)sebelumbind()membersihkan ini.
9.13.4. Apa selanjutnya¶
Memblokir pada accept() berarti server hanya dapat melayani satu klien pada satu waktu. Memblokir pada recv() berarti satu klien yang lambat menghentikan seluruh loop. Jawaban standar pada kamera adalah asyncio -- jalankan setiap koneksi sebagai tugasnya sendiri, biarkan event loop mendistribusikan di antara mereka. Halaman berikutnya mencakup versi asyncio dari semua yang ada di halaman ini.