12.13. TCP sockets¶
TCP sockets come in two shapes that look different but
share the same underlying type: client sockets that
connect() to a remote server, and
server sockets that bind(),
listen(), and
accept() incoming connections. Both
roles use the same socket class
introduced on Socket objects; only the methods
called on them differ.
12.13.1. A TCP client¶
The simplest client opens a connection, sends a request, reads the reply, and closes:
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() runs the three-way
handshake covered on TCP – a reliable stream of bytes and
returns when the connection is open.
send() writes bytes to the
connection; recv() reads up to a
given number of bytes from it. Once the application is
done, close() shuts the connection
down.
The same script wrapped in the
with-statement idiom from
Socket objects, so the socket is closed even if
something raises:
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))
12.13.1.1. Reading until done¶
A single recv() returns up to
the requested number of bytes – it can return fewer,
because TCP is a stream rather than a sequence of
messages. The application has to keep reading until it
has the full reply:
chunks = []
while True:
chunk = s.recv(1024)
if not chunk: # empty bytes -> other side closed
break
chunks.append(chunk)
reply = b"".join(chunks)
The loop ends when recv() returns
an empty bytes. That happens when the other
side has cleanly closed its half of the connection;
the application reads “end of stream” as the same as
“end of message” in this style of protocol.
12.13.1.2. Sending until done¶
The opposite caveat applies to
send(): it may send fewer bytes
than requested, returning the count of bytes actually
written. For large payloads, retry the unsent
remainder:
payload = some_big_bytes
while payload:
n = s.send(payload)
payload = payload[n:]
sendall() does the loop
internally, so most code can just call that and avoid
the manual retry:
s.sendall(some_big_bytes)
12.13.2. A TCP server¶
The server side is four steps: claim a port, switch the socket to listening mode, accept connections one by one, talk on each accepted socket. A minimal 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()
Step by step:
bind()claims a host and port on the camera."0.0.0.0"accepts on any interface; replacing it with a specific IP restricts the listener to that interface.listen()switches the socket from a normal socket to a listening socket. The argument is the backlog – how many pending connections MicroPython will queue while the application is busy. Pick a small number;1is fine for most cases.accept()blocks until a client connects, then returns(conn, addr): a new socket representing this one connection, and the client’s address. The listening socket itself stays open to accept more.All the bytes for the conversation flow through
conn, the new socket. Reads and writes use the samerecv()/send()calls as on the client side.When the client closes,
recv()returnsb""; the inner loop ends and the server closes its end withclose().
The outer while True jumps back to
accept() to wait for the next
client. The server handles one client at a time in this
shape; running multiple clients in parallel needs
either threads or asyncio. The latter is the
subject of the next page.
12.13.3. Common pitfalls¶
Treating recv() as message-shaped. It is not. Two
send(b"hi")calls might arrive as onerecv(4)ofb"hihi", or as tworecv(2)s. The application has to add framing if message boundaries matter – a newline, a length prefix, whatever.Forgetting to retry on short sends. Use
sendall()for anything beyond a few hundred bytes.Forgetting to close the accepted socket. Each
connis a separate socket; closing the listening socket does not close the accepted ones.with-blocks on both make this hard to get wrong:while True: with server.accept()[0] as conn: # ... talk on conn ...
Re-binding to a port still in TIME_WAIT. When a server restarts within a few seconds of closing,
bind()may fail with “address in use” because MicroPython is still holding the port for the previous connection.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)beforebind()clears this.
12.13.4. What’s next¶
Blocking on accept() means the
server can serve only one client at a time. Blocking on
recv() means a single slow client
hangs the whole loop. The standard answer on the camera
is asyncio – run each connection as its own
task, let the event loop dispatch between them. The
next page covers the asyncio versions of everything on
this one.