QUESTION
What state does an fd returned by accept share with the listening fd?
A listening fd owns a local endpoint and pending-connection queues, and exists only to receive new connections. accept creates and returns a new struct socket/file/fd for one connection; subsequent byte-stream state belongs to that accepted fd.
Closing the listener does not end already accepted connections. Conversely, closing an accepted fd does not affect the listener or other connections. Separating fd ownership by kind simplifies shutdown.
STRUCTURE
Structure diagram
A request with a handshake in progress and a connection awaiting accept after handshake completion occupy different queues. accept creates a separate connected-socket fd.
CALL PATH
Call path
Draw the queue for connections processing SYN, the queue for completed connections awaiting accept, the listening socket, and connected sockets in separate boxes.
This diagram is not for memorizing function names. Follow which return value, file descriptor, memory mapping, or wait queue is passed to the next stage.
SOURCE COORDINATES
Linux 6.18.37 LTS source locations
Go beyond the glibc function to the files where the syscall implementation meets kernel objects. Each link points to the original file at the same tag.
| File | Function / structure | What to inspect |
|---|---|---|
| net/socket.c | __sys_socket(), do_accept() | Expose a socket object as a file/fd and install the accepted fd |
| net/ipv4/af_inet.c | inet_bind(), inet_listen() | Set an IPv4 local address and passive state |
| net/ipv4/inet_connection_sock.c | inet_csk_accept() | Detach a completed child socket from the accept queue |
COMPLETE PROGRAM
Complete runnable example
The code below is not pseudocode with explanatory lines omitted. It is a minimal example that can be built and run as a file.
cc -std=c17 -Wall -Wextra -O2 tcp_server.c -o tcp_server01#define _GNU_SOURCE
02#include <arpa/inet.h>
03#include <netinet/in.h>
04#include <stdio.h>
05#include <stdlib.h>
06#include <sys/socket.h>
07#include <unistd.h>
08
09int main(int argc, char **argv)
10{
11 if (argc != 2)
12 return 2;
13 int listener = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
14 int one = 1;
15 setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
16 struct sockaddr_in address = {
17 .sin_family = AF_INET,
18 .sin_port = htons((unsigned short)strtoul(argv[1], NULL, 10)),
19 .sin_addr = { .s_addr = htonl(INADDR_LOOPBACK) }
20 };
21 if (listener < 0 || bind(listener, (struct sockaddr *)&address, sizeof(address)) < 0 ||
22 listen(listener, 128) < 0)
23 return 1;
24
25 int client = accept4(listener, NULL, NULL, SOCK_CLOEXEC);
26 if (client < 0)
27 return 1;
28 char buffer[1024];
29 ssize_t n = read(client, buffer, sizeof(buffer));
30 if (n > 0)
31 write(client, buffer, (size_t)n);
32 shutdown(client, SHUT_WR);
33 close(client);
34 close(listener);
35 return n < 0;
36}
CODE NOTES
Code notes
SOCK_STREAM | SOCK_CLOEXECPrevents exec inheritance when the stream socket file is created. A nonblocking server should also apply SOCK_NONBLOCK atomically during creation/accept.
SO_REUSEADDRSets the local-address reuse policy before bind. This differs in purpose from SO_REUSEPORT; distribution across several listeners is separate.
htonl(INADDR_LOOPBACK)Limits example exposure by binding only to loopback rather than an external interface.
listen(listener, 128)Enters passive state and supplies a hint for the completed-connection queue limit. The effective limit also depends on kernel settings and protocol state.
accept4(listenerRemoves one connection from the listener and creates a separate fd. Do not assume listener file status flags are inherited unchanged by the new fd.
DETAILS
Detailed behavior
One backlog value cannot describe the entire queueing system
TCP distinguishes a request queue for handshakes in progress from a queue of completed handshakes awaiting accept. Syncookies, tcp_max_syn_backlog, somaxconn, and application accept rate create different bottlenecks.
Inspect current queues with ss -lnt and kernel counters.
accept also has retryable failures
A nonblocking listener returns EAGAIN when its queue is empty. A signal can produce EINTR, and a pending network error can appear on the new socket. An edge-triggered listener repeats accept until EAGAIN.
Under EMFILE the listener can remain readable and form a busy loop, so define a recovery strategy such as a reserve fd.
close and shutdown differ
shutdown(SHUT_WR) sends FIN and creates a half-close: no more writes, while peer data can still be read. close drops an fd reference, and protocol termination proceeds according to the last-socket-reference policy.
SO_LINGER can change close blocking and RST semantics, so do not set it without understanding the default.
OBJECTS
Objects and lifetimes
| Object | Creation and release | Values to inspect |
|---|---|---|
listening socket | Prepared by socket-bind-listen; the passive endpoint ends when the listener closes | local addr, state, accept queue |
accepted socket | Created by accept and passes through connection close/TIME-WAIT protocol | 4-tuple, send/receive queue |
socket fd | Created by sock_alloc_file/fd_install and drops its file reference on close | CLOEXEC, NONBLOCK, owner |
FAILURE PATH
Failure conditions and common misconceptions
| Observed symptom | Likely causes | How to verify |
|---|---|---|
| Connections arrive but accept returns none | An fd limit or stalled event loop | ss queue, EMFILE log, loop latency |
| A restart bind returns EADDRINUSE | Another listener or the address policy | ss -lptn, reuse option, namespace |
| The client's final data is lost | Receive draining was omitted on HUP/close | Process buffered data before read returns 0 |
LAB
Verify it yourself
- Connect with nc 127.0.0.1 8080 and inspect echo plus half-close behavior.
- Connect several clients before the listener accepts and observe changes in Recv-Q from ss -lnt.
- Add SOCK_NONBLOCK to accept4 and implement an epoll edge-triggered accept loop.
./tcp_server 8080strace -e trace=socket,setsockopt,bind,listen,accept4,read,write,shutdown,close ./tcp_server 8080PRIMARY REFERENCES