Why Socket Options Exist
Every socket has a set of properties that control its behaviour — buffer sizes, keep-alive behaviour, address reuse, linger on close, and more. These are called socket options, and you can read and modify them using getsockopt() and setsockopt().
Options are organised into levels: SOL_SOCKET (generic socket options), IPPROTO_TCP (TCP-specific), IPPROTO_IP (IP layer). Some options change the kernel’s handling of the socket; others just affect your application’s behaviour.
The API
#include <sys/socket.h>
/*
* Set a socket option.
*
* sockfd — the socket to configure
* level — option level (SOL_SOCKET, IPPROTO_TCP, IPPROTO_IP, ...)
* optname — the option to set (SO_REUSEADDR, SO_KEEPALIVE, ...)
* optval — pointer to the option value
* optlen — size of optval in bytes
*
* Returns 0 on success, -1 on error.
*/
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
/*
* Get a socket option's current value.
*
* optval — buffer to receive the value
* optlen — in: size of buffer; out: actual bytes written
*
* Returns 0 on success, -1 on error.
*/
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
Option Levels
SO_REUSEADDR — Allow Port Reuse
Already discussed in Part 5, but essential enough to cover in detail here. This option allows a new socket to bind to a port that has sockets in TIME_WAIT from a previous run.
#include <sys/socket.h>
#include <stdio.h>
/*
* Set SO_REUSEADDR — ALWAYS do this on server sockets before bind().
*/
int set_reuseaddr(int sockfd)
{
int optval = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
&optval, sizeof(optval)) == -1) {
perror("setsockopt SO_REUSEADDR");
return -1;
}
printf("SO_REUSEADDR enabled\n");
return 0;
}
/* Verify it's set */
void check_reuseaddr(int sockfd)
{
int optval;
socklen_t optlen = sizeof(optval);
if (getsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
&optval, &optlen) == 0) {
printf("SO_REUSEADDR is %s\n", optval ? "enabled" : "disabled");
}
}
SO_KEEPALIVE — Detect Dead Connections
By default, TCP connections can remain ESTABLISHED indefinitely even if the peer machine has crashed or been disconnected. SO_KEEPALIVE enables the kernel to periodically probe the peer to check if it’s still alive.
If keep-alive probes fail, the connection is declared dead and the application’s next read/write gets an error.
#include <sys/socket.h>
#include <netinet/tcp.h>
int setup_keepalive(int sockfd)
{
int optval;
int rc;
/* 1. Enable keep-alive on the socket */
optval = 1;
rc = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
&optval, sizeof(optval));
if (rc == -1) { perror("SO_KEEPALIVE"); return -1; }
/*
* 2. TCP_KEEPIDLE: seconds of inactivity before first probe.
* Default on Linux is 7200 seconds (2 hours!) — too long.
* Set to 60 seconds.
*/
optval = 60;
rc = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE,
&optval, sizeof(optval));
if (rc == -1) { perror("TCP_KEEPIDLE"); return -1; }
/*
* 3. TCP_KEEPINTVL: seconds between subsequent probes.
* Set to 10 seconds.
*/
optval = 10;
rc = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL,
&optval, sizeof(optval));
if (rc == -1) { perror("TCP_KEEPINTVL"); return -1; }
/*
* 4. TCP_KEEPCNT: number of probes before declaring connection dead.
* After 3 failed probes (3 × 10s = 30s after first probe),
* the connection is aborted.
*/
optval = 3;
rc = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT,
&optval, sizeof(optval));
if (rc == -1) { perror("TCP_KEEPCNT"); return -1; }
printf("Keep-alive: enabled (idle=60s, interval=10s, count=3)\n");
printf("Dead connection detected after: 60 + (3 × 10) = 90 seconds\n");
return 0;
}
SO_LINGER — Control close() Behaviour
By default, close() returns immediately and the kernel sends any buffered data in the background. SO_LINGER changes this behaviour.
#include <sys/socket.h>
struct linger {
int l_onoff; /* 0 = disable linger, non-zero = enable */
int l_linger; /* seconds to linger (when l_onoff != 0) */
};
void demo_linger(int sockfd)
{
struct linger lg;
socklen_t optlen = sizeof(lg);
/* Check current linger setting */
getsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lg, &optlen);
printf("Linger: onoff=%d, linger=%d\n", lg.l_onoff, lg.l_linger);
/* --- Option 1: Disable linger (default behaviour) --- */
lg.l_onoff = 0;
lg.l_linger = 0;
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg));
/* close() returns immediately; kernel drains buffer in background */
/* --- Option 2: Enable linger with timeout --- */
lg.l_onoff = 1;
lg.l_linger = 5; /* wait up to 5 seconds */
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg));
/*
* close() blocks until:
* (a) all data in send buffer is ACK'd by peer, OR
* (b) the linger timeout (5s) expires.
* If timeout expires, remaining data is discarded and RST is sent.
*/
/* --- Option 3: Linger with zero timeout (immediate RST) --- */
lg.l_onoff = 1;
lg.l_linger = 0;
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg));
/*
* close() immediately sends RST, discarding any unsent data.
* The connection is abruptly terminated — no graceful FIN exchange.
* Useful for intentionally resetting a misbehaving connection.
*/
}
SO_SNDBUF and SO_RCVBUF — Buffer Sizes
The kernel has send and receive buffers for each socket. The default sizes are determined by system settings (typically 212KB on Linux). Increasing these can improve throughput on high-bandwidth, high-latency connections (the “bandwidth-delay product” problem).
#include <sys/socket.h>
#include <stdio.h>
void tune_buffers(int sockfd)
{
int optval;
socklen_t optlen;
/* --- Check current buffer sizes --- */
optlen = sizeof(optval);
getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &optval, &optlen);
printf("Current send buffer: %d bytes\n", optval);
getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &optval, &optlen);
printf("Current recv buffer: %d bytes\n", optval);
/* --- Increase send buffer to 256KB --- */
optval = 256 * 1024;
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF,
&optval, sizeof(optval)) == -1) {
perror("SO_SNDBUF");
}
/* --- Increase receive buffer to 256KB --- */
optval = 256 * 1024;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF,
&optval, sizeof(optval)) == -1) {
perror("SO_RCVBUF");
}
/* Note: Linux doubles the value you set (for internal overhead).
* If you set 256KB, getsockopt may return 512KB.
* The kernel also enforces a maximum (rmem_max / wmem_max). */
/* Re-read to verify */
optlen = sizeof(optval);
getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &optval, &optlen);
printf("New send buffer: %d bytes\n", optval);
}
System-wide buffer limits:
# Check current maximum buffer sizes:
cat /proc/sys/net/core/rmem_max # max receive buffer
cat /proc/sys/net/core/wmem_max # max send buffer
# Temporarily increase (takes effect immediately):
echo 8388608 | sudo tee /proc/sys/net/core/rmem_max
echo 8388608 | sudo tee /proc/sys/net/core/wmem_max
# Permanently (survives reboot) — edit /etc/sysctl.conf:
# net.core.rmem_max = 8388608
# net.core.wmem_max = 8388608
# Then: sudo sysctl -p
TCP_NODELAY — Disable Nagle Algorithm
Covered in Part 5 in context, here is the full option usage with read-back verification:
#include <netinet/tcp.h>
#include <stdio.h>
int set_tcp_nodelay(int sockfd, int enable)
{
int flag = enable ? 1 : 0;
if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
&flag, sizeof(flag)) == -1) {
perror("TCP_NODELAY");
return -1;
}
/* Verify */
int result;
socklen_t optlen = sizeof(result);
getsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &result, &optlen);
printf("TCP_NODELAY is %s\n", result ? "enabled (Nagle OFF)" : "disabled (Nagle ON)");
return 0;
}
SO_ERROR — Read and Clear Pending Error
Asynchronous errors (like a TCP connection being reset by the peer while you weren’t reading) are stored in the socket’s error queue. SO_ERROR retrieves and clears this pending error. This is particularly important with non-blocking sockets and after using select()/poll() to wait for writability (which is how non-blocking connect works).
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
int check_socket_error(int sockfd)
{
int soerr;
socklen_t optlen = sizeof(soerr);
/*
* getsockopt SO_ERROR:
* - Returns the pending error code (like errno values)
* - Clears the error after reading
* - Returns 0 if no error is pending
*/
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &soerr, &optlen) == -1) {
perror("getsockopt SO_ERROR");
return -1;
}
if (soerr != 0) {
fprintf(stderr, "Socket has pending error: %s\n", strerror(soerr));
return soerr;
}
printf("No pending socket error\n");
return 0;
}
/*
* Typical use: after non-blocking connect(), use select() to wait
* for writability, then check SO_ERROR to know if connect succeeded.
*/
int nonblocking_connect_result(int sockfd)
{
int soerr = check_socket_error(sockfd);
if (soerr == 0) {
printf("connect() succeeded\n");
return 0;
} else if (soerr == ECONNREFUSED) {
fprintf(stderr, "connect() refused — no server at that port\n");
return -1;
} else if (soerr == ETIMEDOUT) {
fprintf(stderr, "connect() timed out — host unreachable?\n");
return -1;
} else {
fprintf(stderr, "connect() failed: %s\n", strerror(soerr));
return -1;
}
}
SO_RCVTIMEO and SO_SNDTIMEO — I/O Timeouts
These options set a timeout for receive and send operations. If a recv() or send() blocks longer than the timeout, it returns -1 with errno set to EAGAIN or EWOULDBLOCK.
#include <sys/socket.h>
#include <sys/time.h>
int set_io_timeout(int sockfd, int seconds, int microseconds)
{
struct timeval tv;
tv.tv_sec = seconds;
tv.tv_usec = microseconds;
/* Set receive timeout */
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO,
&tv, sizeof(tv)) == -1) {
perror("SO_RCVTIMEO"); return -1;
}
/* Set send timeout */
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO,
&tv, sizeof(tv)) == -1) {
perror("SO_SNDTIMEO"); return -1;
}
printf("I/O timeout set to %d.%06d seconds\n", seconds, microseconds);
return 0;
}
/* Example: read with 5-second timeout */
void read_with_timeout(int sockfd)
{
char buf[256];
ssize_t n;
set_io_timeout(sockfd, 5, 0); /* 5 second timeout */
n = recv(sockfd, buf, sizeof(buf), 0);
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("recv() timed out — no data within 5 seconds\n");
} else {
perror("recv");
}
} else if (n == 0) {
printf("Peer closed connection\n");
} else {
printf("Received %zd bytes\n", n);
}
}
Quick Reference: Common Socket Options
Interview Questions & Answers
Chapter 61 Series Complete!
You’ve covered all 7 parts of Sockets: Advanced Topics
