A TCP server calls socket(), bind(), listen(), and then accept(). A client connects and the server receives a new file descriptor from accept(). The server finishes handling that client, closes the client socket, and calls accept() again. What is happening on the second call to accept()?
AThe server is rebinding to a new port to accept a second connection
BThe server is waiting on the original listening socket for a second client to connect
CThe server is closing the listening socket and terminating
DThe server is reconnecting to the first client on a new descriptor
This reveals the critical distinction between the listening socket and the connected socket. The first accept() call returned a new, dedicated file descriptor for client A's conversation — that is now closed. But the original listening socket (bound to the port via bind() and listen()) is still open and still watching for new connections. The second accept() call blocks on that same listening socket, waiting for a second client. The listening socket persists for the server's lifetime; connected sockets come and go per client.
Question 2 Multiple Choice
A UDP server is being designed to receive datagrams from multiple clients. Which of the following correctly describes the socket API sequence it should use?
Asocket() → bind() → listen() → accept() → recvfrom(), identical to a TCP server
Bsocket() → bind() → recvfrom() / sendto(), skipping listen() and accept() because UDP is connectionless
Csocket() → connect() → recv(), since UDP requires a connection before receiving
Dsocket() → listen() → sendto(), since UDP servers only send, never receive
UDP is connectionless: there is no handshake, no established connection state, and therefore no need for listen() (which marks a socket as willing to accept connection requests) or accept() (which completes a TCP handshake and returns a connected socket). A UDP server simply binds to a port and uses recvfrom() to receive datagrams — each call returns the sender's address so the server can respond with sendto(). The simplicity comes at the cost of reliability: UDP provides no guaranteed delivery, ordering, or duplicate elimination.
Question 3 True / False
When a TCP server calls accept(), it gets back a new socket file descriptor that is distinct from the original listening socket — one specifically for communicating with the newly connected client.
TTrue
FFalse
Answer: True
This distinction is one of the most important concepts in socket programming. The listening socket (created by socket(), bound via bind(), marked passive via listen()) remains open to receive future connection requests. accept() returns a separate, connected socket representing exactly one client conversation. Data is read from and written to this connected socket; the listening socket is not used for data transfer. When the conversation ends, you close the connected socket — not the listening socket, which continues accepting new clients.
Question 4 True / False
A UDP server should call listen() before calling recvfrom(), just as a TCP server calls listen() before accept().
TTrue
FFalse
Answer: False
listen() is specific to connection-oriented (TCP) sockets. It marks a socket as passive, meaning it will accept incoming connection requests via accept(). UDP is connectionless — there are no connection requests to accept. A UDP server simply calls socket(), bind() to a port, and then recvfrom() to receive incoming datagrams directly. Calling listen() on a UDP socket would either fail or have no meaningful effect depending on the OS. The API asymmetry between TCP and UDP reflects their fundamentally different transport models.
Question 5 Short Answer
Why does a simple TCP server that loops — calling accept(), handling one client completely, then looping back to accept() — fail to serve multiple simultaneous clients well, and what are the standard strategies to fix this?
Think about your answer, then reveal below.
Model answer: Because sockets block by default, a server stuck inside recv() (or any other blocking call) waiting for client A to send data cannot simultaneously call accept() to receive client B's connection. Client B's connection sits in the listen backlog queue and is not served until client A's entire session finishes. Standard strategies include: (1) fork/thread per connection — spawn a new process or thread for each accepted client so blocking in one doesn't affect others; (2) non-blocking I/O with select(), poll(), or epoll() — monitor multiple socket file descriptors simultaneously and only read/write when data is available; (3) async I/O frameworks — event loops (like libuv or asyncio) that multiplex many connections in a single thread.
The blocking nature of the default socket API is a feature for simple cases but a fundamental limitation for production servers. The choice of concurrency strategy involves tradeoffs: threads have lower latency but higher memory cost per connection; select()/poll() scale to many connections but require more complex code; async frameworks hide the complexity but add abstraction. Understanding the raw blocking behavior is what makes these tradeoffs legible — and it explains why high-performance servers like nginx use event-loop architectures rather than one-thread-per-connection models.