Fundamentals 14 min read

Why Does a Server Need to Call listen()? Uncovering Linux’s Socket Queue Mechanics

This article explains why a server must invoke listen() after bind(), detailing the kernel's listen system call, how it creates and initializes the full‑connection and half‑connection queues, and how their lengths are calculated based on backlog, somaxconn, and tcp_max_syn_backlog parameters.

Bin's Tech Cabin
Bin's Tech Cabin
Bin's Tech Cabin
Why Does a Server Need to Call listen()? Uncovering Linux’s Socket Queue Mechanics

Hello, I'm bin. My public account mainly analyzes source code of well‑known network frameworks, so you are likely familiar with typical network programming patterns.

But have you ever wondered why, after binding an IP address and port, a server still needs to call listen() before it can accept client connections?

This article shares an excellent explanation originally written by Fei, covering the inner workings of the listen call.

1. Creating a socket

The server first creates a socket by calling the socket function, which returns a file descriptor in user space but corresponds to a complex kernel object.

2. Kernel executes listen

2.1 listen system call

The listen system call is defined in net/socket.c:

SYSCALL_DEFINE2(listen, int, fd, int, backlog){
  sock = sockfd_lookup_light(fd, &err, &fput_needed);
  if (sock) {
    somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
    if ((unsigned int)backlog > somaxconn)
      backlog = somaxconn;
    err = sock->ops->listen(sock, backlog);
    ...
  }
}

The kernel looks up the socket object from the user‑space file descriptor, retrieves the net.core.somaxconn value, and uses the smaller of backlog and somaxconn for the next step.

2.2 Protocol‑stack listen

The socket’s ops->listen points to inet_listen in net/ipv4/af_inet.c:

int inet_listen(struct socket *sock, int backlog){
  if (old_state != TCP_LISTEN) {
    err = inet_csk_listen_start(sk, backlog);
  }
  sk->sk_max_ack_backlog = backlog;
}

This sets the maximum length of the full‑connection queue ( sk_max_ack_backlog).

If you encounter full‑connection queue overflow in production, you need to adjust both the backlog passed to listen() and the net.core.somaxconn sysctl.

2.3 Receive‑queue definition

The icsk->icsk_accept_queue is a request_sock_queue that holds both the full‑connection and half‑connection queues.

struct inet_connection_sock {
  struct inet_sock   icsk_inet;
  struct request_sock_queue icsk_accept_queue;
  ...
}
struct request_sock_queue {
  struct request_sock *rskq_accept_head;
  struct request_sock *rskq_accept_tail;
  struct listen_sock *listen_opt;
  ...
};

The full‑connection queue is a simple FIFO list, while the half‑connection queue is represented by listen_opt, a listen_sock structure.

2.4 Receive‑queue allocation and initialization

The function inet_csk_listen_start calls reqsk_queue_alloc to allocate and initialise the queue:

int inet_csk_listen_start(struct sock *sk, const int nr_table_entries){
  ...
  int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);
  ...
}
The half‑connection queue stores pointers in a hash table; the actual request_sock objects are allocated during the three‑way handshake.

2.5 Half‑connection queue length calculation

The length is computed inside reqsk_queue_alloc:

int reqsk_queue_alloc(struct request_sock_queue *queue,
        unsigned int nr_table_entries){
  nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
  nr_table_entries = max_t(u32, nr_table_entries, 8);
  nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
  for (lopt->max_qlen_log = 3;
       (1 << lopt->max_qlen_log) < nr_table_entries;
       lopt->max_qlen_log++);
  ...
}

The steps are:

Take the minimum of the requested backlog and sysctl_max_syn_backlog.

Ensure the value is at least 8.

Round up to the next power of two.

Example: with net.core.somaxconn = 128, net.ipv4.tcp_max_syn_backlog = 8192, and backlog = 5, the half‑connection queue length becomes 16. If backlog = 512, the length becomes 256.

To increase the half‑connection queue you must consider somaxconn , backlog , and tcp_max_syn_backlog together; changing only one parameter is insufficient.

Conclusion

Server‑side socket programs follow the sequence: bind → listen → accept. The listen call’s primary role is to allocate and initialise the receive queues: a FIFO full‑connection queue and a hash‑based half‑connection queue.

Understanding these details helps diagnose and resolve connection‑queue overflows in production environments.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendSocketlistennetwork-programminglinux-kernel
Bin's Tech Cabin
Written by

Bin's Tech Cabin

Original articles dissecting source code and sharing personal tech insights. A modest space for serious discussion, free from noise and bureaucracy.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.