Overview
When you write a server program, two important things happen before it can accept connections: you set socket options (like SO_REUSEADDR), and then you call accept() which creates a new socket for each client. A natural question arises: does that new socket inherit all the settings you applied to the listening socket?
The answer is: it depends on what kind of setting it is. This tutorial explains the rules clearly.
Key Terms
1. Why SO_REUSEADDR Is Important
When you stop and restart a server, the old TCP connection can stay in a TIME_WAIT state for up to 2 minutes. During this time, if you try to bind() to the same port again, you get an error: “Address already in use”.
The SO_REUSEADDR socket option tells the kernel: “Allow me to bind to this address/port even if it is still in TIME_WAIT.” This is why almost every TCP server sets this option before calling bind().
Code: Correct Server Socket Setup
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int sockfd, optval;
struct sockaddr_in addr;
/* Step 1: Create the socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/* Step 2: Set SO_REUSEADDR BEFORE bind() */
optval = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
&optval, sizeof(optval)) == -1) {
perror("setsockopt SO_REUSEADDR");
exit(EXIT_FAILURE);
}
/* Step 3: Bind to address */
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
/* Step 4: Start listening */
if (listen(sockfd, 5) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("Server listening on port 8080\n");
/* ... accept loop ... */
return 0;
}
Key rule: Always call setsockopt(SO_REUSEADDR) before bind(). Setting it after bind has no effect on the current bind.
2. Two Sockets: Listening vs. Connected
A TCP server actually uses two different sockets:
socket(), configured with setsockopt(), bound with bind(), set passive with listen().accept()
accept(). A brand-new socket representing ONE specific client connection.read()/write(). A new one is created per client.The critical question is: what does the connected socket inherit from the listening socket?
3. What Is NOT Inherited by accept()
On Linux, the following are not inherited by the new socket returned from accept(). This is actually a good thing — it prevents unwanted behavior from leaking into client connections.
| What is NOT inherited | How to set it | Why it matters |
|---|---|---|
O_NONBLOCK |
fcntl(fd, F_SETFL, O_NONBLOCK) |
Non-blocking I/O mode |
O_ASYNC |
fcntl(fd, F_SETFL, O_ASYNC) |
Signal-driven I/O |
FD_CLOEXEC |
fcntl(fd, F_SETFD, FD_CLOEXEC) |
Close on exec() |
F_SETOWN (owner PID) |
fcntl(fd, F_SETOWN, pid) |
Target process for SIGIO |
F_SETSIG (custom signal) |
fcntl(fd, F_SETSIG, signum) |
Signal for async I/O notification |
In simple terms: open file status flags (set via fcntl F_SETFL) and file descriptor flags (set via fcntl F_SETFD) are NOT inherited.
4. What IS Inherited by accept()
The new connected socket does inherit a copy of most socket options set via setsockopt(). This includes important options like:
SO_KEEPALIVE— keep-alive probesSO_LINGER— linger on close behaviorSO_RCVBUF/SO_SNDBUF— receive/send buffer sizesTCP_NODELAY— disable Nagle algorithm- Most other
setsockopt()options atSOL_SOCKETandIPPROTO_TCPlevel
So if you set TCP_NODELAY on the listening socket, every connected socket you get from accept() will also have TCP_NODELAY enabled. This saves you from calling setsockopt() again for each client.
5. Portability Issue and How to Handle It
The behavior described above is for Linux. On some other UNIX systems (like older BSD systems), O_NONBLOCK and O_ASYNC are inherited from the listening socket. This creates a portability trap.
O_NONBLOCK and O_ASYNC on the socket returned by accept(). Do not rely on whether these flags are inherited or not.Code: Making accept() Socket Non-Blocking (Portable Way)
#include <sys/socket.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
/* Helper: set a socket to non-blocking mode */
static void set_nonblocking(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl F_GETFL");
exit(EXIT_FAILURE);
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL O_NONBLOCK");
exit(EXIT_FAILURE);
}
}
int main(void)
{
int listenfd, connfd;
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(client_addr);
listenfd = socket(AF_INET, SOCK_STREAM, 0);
/* ... bind(), listen() ... */
connfd = accept(listenfd, (struct sockaddr *)&client_addr, &addrlen);
if (connfd == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
/*
* Portability: explicitly set O_NONBLOCK on the connected socket.
* On Linux, O_NONBLOCK is NOT inherited from the listening socket.
* On some BSD systems, it IS inherited.
* Explicit setting handles both cases correctly.
*/
set_nonblocking(connfd);
printf("Client connected, connfd=%d (non-blocking)\n", connfd);
/* ... read()/write() on connfd ... */
return 0;
}
Code: Making accept() Socket Close-on-Exec (Portable Way)
#include <sys/socket.h>
#include <fcntl.h>
/*
* FD_CLOEXEC is also NOT inherited by the accept() socket on Linux.
* If your server forks and execs child processes, you want connected
* sockets to be closed automatically in the child's exec().
* Set it explicitly after accept().
*/
int accept_cloexec(int listenfd)
{
int connfd;
struct sockaddr_storage client_addr;
socklen_t addrlen = sizeof(client_addr);
int flags;
connfd = accept(listenfd, (struct sockaddr *)&client_addr, &addrlen);
if (connfd == -1)
return -1;
/* Set close-on-exec flag on the new socket */
flags = fcntl(connfd, F_GETFD, 0);
if (flags == -1 || fcntl(connfd, F_SETFD, flags | FD_CLOEXEC) == -1) {
close(connfd);
return -1;
}
return connfd;
}
/*
* Linux 2.6.28+ alternative: use accept4() which can set flags atomically.
* This avoids a race condition between accept() and fcntl().
*/
int accept_cloexec_atomic(int listenfd)
{
struct sockaddr_storage client_addr;
socklen_t addrlen = sizeof(client_addr);
/* SOCK_CLOEXEC sets FD_CLOEXEC atomically */
return accept4(listenfd, (struct sockaddr *)&client_addr,
&addrlen, SOCK_CLOEXEC | SOCK_NONBLOCK);
}
Tip: On Linux, prefer accept4() with SOCK_NONBLOCK and/or SOCK_CLOEXEC flags. This sets both atomically, which avoids the tiny race window between accept() and the subsequent fcntl() call.
6. Summary: Inheritance Rules at a Glance
O_ASYNC (fcntl F_SETFL)
FD_CLOEXEC (fcntl F_SETFD)
F_SETOWN (owner PID)
F_SETSIG (signal number)
SO_LINGER
SO_RCVBUF / SO_SNDBUF
TCP_NODELAY
Most setsockopt() options
Interview Questions
It allows a server to bind to a port that is still in the TCP TIME_WAIT state from a previous connection. It must be set before calling bind(). Almost every TCP server should set this option so it can restart quickly without waiting 2 minutes for TIME_WAIT to expire.
No. On Linux, O_NONBLOCK is an open file status flag set via fcntl F_SETFL and it is NOT inherited. The connected socket starts in blocking mode regardless of the listening socket’s flags. You must explicitly call fcntl(connfd, F_SETFL, O_NONBLOCK) or use accept4() with SOCK_NONBLOCK.
Most options set via setsockopt() are inherited — for example, SO_KEEPALIVE, SO_RCVBUF, SO_SNDBUF, SO_LINGER, and TCP_NODELAY. This means you can configure these on the listening socket once and all accepted client sockets will automatically get the same settings.
accept4() is a Linux-specific extension that accepts a flags argument. You can pass SOCK_NONBLOCK and/or SOCK_CLOEXEC to set these flags atomically on the returned socket descriptor. This avoids the race condition that exists between accept() and a subsequent fcntl() call in multi-threaded servers.
In a server that handles clients by forking a child process and then exec-ing a helper program, you do not want the connected client socket to remain open in the exec’d process. Setting FD_CLOEXEC on the connected socket ensures it is automatically closed when the child calls exec(), preventing resource leaks.
Since the POSIX standard (SUSv3) is silent on whether file status flags like O_NONBLOCK are inherited by the accept() socket, different UNIX implementations behave differently — some inherit them, some do not. For portable code, always explicitly set or clear these flags on the socket returned by accept() rather than relying on inheritance behavior.
