4.3.28 · D5Computer Networks

Question bank — Socket programming — TCP server - client, UDP server - client in Python

2,554 words12 min readBack to topic

Every answer below is a reason, not a bare yes/no — because on an exam or in an interview the reason is the mark.


First, the vocabulary these traps assume

Most traps below hinge on four words. We define them here, on this page, so you never have to leave to understand a trap.


The systems picture: how a byte stream fragments and coalesces

Almost every TCP trap ("add your own framing", "loop until b''") comes from one hidden mechanism: kernel send/receive buffers. Understand this diagram once and the traps become obvious.


A running narrative: one client's connect → sendall cycle

Before the traps, walk this concrete story once. It is the scaffold every trap plugs into.


True or false — justify

TCP guarantees that one send on the client becomes exactly one recv on the server.
False. send only copies bytes into the kernel's send buffer; the kernel slices them into segments on its own schedule, so one send can be split across many recvs or several sends merged into one. Boundaries are yours to add — see Message framing / length-prefix protocol.
UDP guarantees that one sendto becomes exactly one recvfrom (if it arrives at all).
True. UDP preserves message boundaries: each datagram is a self-contained unit the kernel never splits or merges. It may be lost, duplicated, or reordered, but its contents arrive whole or not at all.
The listening socket returned by socket() is the same object you read and write client data on.
False. In TCP, accept returns a new per-client socket (conn) named by the full 4-tuple; the original listening socket stays passive so it can accept more clients. Read/write on conn, not the listener.
A recv returning b'' just means the peer had nothing to say this time.
False. Empty bytes is the EOF signal: the peer sent a FIN and will never send more. "Nothing yet" would block instead. Break your loop, or it busy-spins forever.
sendall and send are interchangeable.
False. send may copy only part of your buffer (returning the count actually queued); sendall loops until every byte is queued (or raises). For anything beyond a few bytes, prefer sendall.
The client must always bind to a port before it can talk.
False. The client normally skips bind; on connect/sendto the OS auto-assigns an ephemeral port (a temporary high-numbered port it recycles later). Only the side others must find — the server — needs a fixed, known port. See Ports and IP addressing.
Calling connect() on a UDP socket performs a handshake with the server.
False. UDP connect does no network handshake — it only fixes the default peer locally so you can use send/recv instead of sendto/recvfrom. Zero packets leave the machine. Contrast TCP three-way handshake.
listen(5) means the server can handle at most 5 clients total.
False. The 5 is the backlog, the waiting-room depth for connections not yet accepted. As you accept and process them, more can queue. It caps pending connections, not total or concurrent ones.
Two sockets can never use the same local port number at the same time.
False. A connection is named by the full 4-tuple (local IP, local port, remote IP, remote port). Many clients hit the server's single port 9000; each conn differs in the remote IP/port, so they coexist.
SO_REUSEADDR lets two servers actively serve on the same port simultaneously.
False (mostly). SO_REUSEADDR mainly lets you bind a port stuck in TIME_WAIT from a prior instance, so a restart doesn't fail with "Address already in use." It does not let two live listeners share the address — that's a different option (see the SO_REUSEPORT edge case below). See TIME_WAIT and SO_REUSEADDR.

Spot the error

c.send("hello") where c is a connected TCP socket.
Sockets carry bytes, not str. This raises TypeError. Fix: c.send(b"hello") or c.send("hello".encode()), and .decode() on the receiving side.
UDP server code that calls s.listen(5) then conn, addr = s.accept().
listen/accept belong to TCP (connection setup). UDP is connectionless — remove them and use recvfrom/sendto, which carry the address in each call.
TCP server that calls s.recv(1024) directly on the listening socket s.
You must recv on the socket returned by accept (the per-client conn), not the listening socket. The listener only produces connections; it carries no application data.
A loop while True: data = conn.recv(1024) that processes data but never checks for b''.
When the peer closes (FIN), recv returns b'' on every future call and the loop busy-spins. Add if not data: break to treat it as EOF.
Client sends b"PING" then immediately conn.recv expecting exactly b"PONG", assuming a full reply arrives in one call.
The kernel may deliver the reply in pieces. You must loop recv and accumulate until your framing says the message is complete (length-prefix or delimiter). See Message framing / length-prefix protocol.
Server does s.bind(('127.0.0.1', 9000)) but the client connects to the machine's LAN IP.
Binding to 127.0.0.1 (loopback) only accepts connections from the same machine. To accept from other hosts, bind to '0.0.0.0' (all IPv4 interfaces) or the specific LAN IP.
Reusing the same UDP socket to sendto client A, then blindly sendto the reply to a hard-coded address instead of the addr from recvfrom.
You'd reply to the wrong host. In UDP you must send back to the exact addr that recvfrom returned, since no connection remembers the peer for you.

Why questions

Why does a TCP server need both listen and accept when a client needs neither?
listen marks the socket passive (it opens the waiting room) and accept pulls one connection out of that queue, returning a dedicated socket. The client is active — it dials with connect, which needs no queue.
Why does UDP have no accept/connect/listen in the basic flow?
There is no connection to set up. Each datagram is self-addressed, so the destination travels inside every sendto rather than being established once by a handshake.
Why does accept return a new socket instead of reusing the listener?
So the listener keeps accepting other clients while the new socket handles this one conversation. One listener → many per-client sockets, each a distinct 4-tuple.
Why is the 1024 in recv(1024) a maximum, not a fixed size?
recv just drains whatever bytes currently sit in the kernel receive buffer, up to 1024. It might return fewer (buffer had less) — the number is a buffer cap, never a message length.
Why do we prefer UDP for things like DNS lookups, gaming, and video calls?
These value low latency over perfect delivery — a late packet is worse than a lost one, and there's no handshake tax or retransmit stall. See UDP vs TCP tradeoffs and DNS.
Why does restarting a TCP server too fast give "Address already in use"?
The just-closed connection sits in TIME_WAIT holding its 4-tuple so late stray packets can't corrupt a new connection. SO_REUSEADDR tells the OS it's safe to bind over that lingering state. See TIME_WAIT and SO_REUSEADDR.
Why can a single-threaded server that just loops accept→handle→accept starve clients?
While it handles one client, others sit un-accepted in the backlog queue. To serve many at once you need select / asyncio for concurrent servers or threads/processes.

Edge cases

What happens if a UDP client sends "A", "B", "C" and one is lost in the network?
The server reads only the survivors, possibly out of order — UDP does not detect, retransmit, or reorder. Add sequence numbers/acks yourself if that matters.
What does the server read if a TCP client does three sends of "A","B","C" back-to-back?
Possibly one recv returning b"ABC", or a split like b"AB" then b"C" — the kernel buffered them adjacently with no boundaries. Framing is the only way to recover three messages.
What happens if you recv on a TCP socket whose peer has crashed without closing cleanly?
With no FIN sent, you may block indefinitely, or eventually get a ConnectionResetError/timeout depending on OS keepalives. Set timeouts or application-level heartbeats to detect dead peers.
What does binding a server to port 0 do?
The OS picks a free ephemeral port for you; read it back with getsockname(). Handy for tests, but clients must then be told which port was chosen.
Is UDP truly reliable on localhost?
Loopback rarely drops, which tempts you to trust UDP — but the same code on a real network will drop, duplicate, and reorder. Never let localhost behaviour convince you UDP is reliable.
What happens if the server never calls accept but clients keep connecting past the backlog?
Once the backlog queue is full, new connection attempts are refused or dropped (client sees a timeout or connection-refused). The waiting room is finite.
What is the difference between SO_REUSEADDR and SO_REUSEPORT, and why does it matter?
SO_REUSEADDR mainly lets you rebind a port lingering in TIME_WAIT (restart quickly); it does not grant two live listeners the same port. SO_REUSEPORT (Linux) does let multiple sockets bind the same IP:port simultaneously, and the kernel load-balances incoming connections across them — the real tool for multi-process concurrent servers. Confusing the two is a classic interview trap.
How does binding an IPv6 server socket differ from IPv4 on a dual-stack host?
The IPv6 wildcard is '::' (all IPv6 interfaces), the analogue of IPv4's '0.0.0.0'. On many dual-stack systems a socket bound to '::' also accepts IPv4 clients via IPv4-mapped addresses (shown as ::ffff:1.2.3.4) unless the IPV6_V6ONLY option is set. So '::' may or may not cover IPv4 depending on that flag — never assume; set IPV6_V6ONLY explicitly if you care.
Recall One-line self-test before you close this page

Say aloud: TCP = stream (kernel buffers, no boundaries → frame it), recv b'' = FIN/EOF; UDP = datagrams, boundaries kept, may drop/reorder; client gets an ephemeral port, rarely binds; accept births a new socket named by the 4-tuple; REUSEADDR ≠ REUSEPORT. If any clause surprised you, revisit that trap above.


Related: UDP vs TCP tradeoffs · Message framing / length-prefix protocol · TCP three-way handshake · Ports and IP addressing · TIME_WAIT and SO_REUSEADDR · select / asyncio for concurrent servers · DNS