Socket backlog
2026-03-10 22:45 Diff

Теги: linux, nginx, freebsd, accept(), setsockopt, tcp_defer_accept, deferred, listen, tcp handshake, tcp_listen, backlog, listen(), backlog size, proxy_pass, netstat -l, netstat, ss -ln, netlink api, netlink_sock_diag, sock_diag(7), recv-q, send-q, net/ipv4/tcp_diag.c

Преамбула: как сервер принимает подключения?

Сервер делает так: listen_socket_fd = socket(AF_INET, SOCK_STREAM, 0) – создаём сокет; setsockopt(listen_socket_fd, _flag_, _value_) – настраиваем его (необязательно); bind(listen_socket_fd, _address_) – привязываем адрес, где будем слушать; listen(listen_socket_fd, **_backlog_size_**) – говорим, что это «слушающий сокет»; int connected_socket_fd = accept(listen_socket_fd, NULL, 0) – садимся и ждём клиентов.

На блокирующих (по-умолчанию) сокетах приложение-сервер висит в accept() до прихода соединения. Если в setsockopt указано TCP_DEFER_ACCEPT (например, за установку этого флага отвечает параметр deferred в директиве listen в конфигурации Nginx), то управление приложению из вызова accept() возвращается, только когда пришли первые данные. Иначе – сразу после того, как произошел tcp handshake.

Accept() возвращает файл-дескриптор нового сокета – сокета соединения. Это два разных сокета: первый (слушающий) имеет единственное состояние TCP_LISTEN, второй – все состояния, кроме TCP_LISTEN.

Все подключения до момента accept() помещаются в очередь, именуемую backlog. Очередь привязана к слушающему сокету, при этом в параметрах listen() указывается максимальная длина этой очереди – backlog size. При переполнении очереди соединение сразу отбрасывается (при proxy_pass в Nginx, например, получим ошибку 502). Очень часто разные приложения-сервера выносят эту настройку в свою конфигурацию.

Как сказал бы Тони Роббинс будь он админом, а не коучем:

«если приложение перестаёт accept()'ить соединения, то растёт очередь в беклоге».

Это была преамбула, а теперь фабула.

Долгое время админы, пришедшие в Linux с FreeBSD плакали, что нет возможности мониторить очередь сокета, вспоминая netstat -L, которого нет в Linux. Объяснялось это весьма мутной документацией netstat в этой части:

Recv-Q The count of bytes not copied by the user program connected to this socket. Send-Q The count of bytes not acknowledged by the remote host.

Я решил посмотреть, что делает ss -ln

socket(PF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG) = 3 sendto(3, "(\0\0\0\24\0\1\3@\342\1\0\0\0\0\0\1\0\0\0\200\4\0\0\0\0\0\0\25\0\0\0"..., 40, 0, NULL, 0) = 40 recvfrom(3, "L\0\0\0\24\0\2\0@\342\1\0\36+\0\0\1\2\7\0'`\0\0\300\3272\35\20\210\377\377"..., 8192, 0, {sa_family=AF_NETLINK, pid=0, groups=00000000},

Ага, информацию ss берёт из ядра через netlink API. Поиск по ключевому слову NETLINK_SOCK_DIAG приводит нас в ман sock_diag(7), в середине которого встречаем искомое:

udiag_rqueue For listening sockets: the number of pending connections. The length of the array associated equal to this value. For established sockets: the amount of data in incoming queue. udiag_wqueue For listening sockets: the backlog length which equals to the value passed as the second argu‐ ment to listen(2). For established sockets: the amount of memory available for sending.

В переводе на русский получается следующее: Если сокет слушающий, то Recv-Q обозначает длину очереди в соединениях, а Send-Q обозначает backlog_size, указанный в listen().

Если сокет – активный сокет соединения, то Recv-Q обозначает количество принятых байт, но не прочитанных приложением. А Send-Q – размер доступного пространства для отправки.

Подтверждается это кусками кода из ядра net/ipv4/tcp_diag.c:

if (sk->sk_state == TCP_LISTEN) { r->idiag_rqueue = sk->sk_ack_backlog; r->idiag_wqueue = sk->sk_max_ack_backlog; } else { r->idiag_rqueue = max_t(int, tp->rcv_nxt - tp->copied_seq, 0); r->idiag_wqueue = tp->write_seq - tp->snd_una; }

net/unix/diag.c:

if (sk->sk_state == TCP_LISTEN) { rql.udiag_rqueue = sk->sk_receive_queue.qlen; rql.udiag_wqueue = sk->sk_max_ack_backlog; } else { rql.udiag_rqueue = (u32) unix_inq_len(sk); rql.udiag_wqueue = (u32) unix_outq_len(sk); }

Итого, ss -ln – то, что вам нужно при диагностике подключений к серверу и теперь вы знаете, как интерпретировать его вывод.

Есть вопрос? Напишите в комментариях!