0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Теги: 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</p>
1
<p>Теги: 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</p>
2
<h3>Преамбула: как сервер принимает подключения?</h3>
2
<h3>Преамбула: как сервер принимает подключения?</h3>
3
<p>Сервер делает так: 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) - садимся и ждём клиентов.</p>
3
<p>Сервер делает так: 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) - садимся и ждём клиентов.</p>
4
<p>На блокирующих (по-умолчанию) сокетах приложение-сервер висит в<strong>accept()</strong>до прихода соединения. Если в<strong>setsockopt</strong>указано<strong>TCP_DEFER_ACCEPT</strong>(например, за установку этого флага отвечает параметр<strong>deferred</strong>в директиве<strong>listen</strong>в конфигурации<strong>Nginx</strong>), то управление приложению из вызова<strong>accept()</strong>возвращается, только когда пришли первые данные. Иначе - сразу после того, как произошел<strong>tcp handshake</strong>.</p>
4
<p>На блокирующих (по-умолчанию) сокетах приложение-сервер висит в<strong>accept()</strong>до прихода соединения. Если в<strong>setsockopt</strong>указано<strong>TCP_DEFER_ACCEPT</strong>(например, за установку этого флага отвечает параметр<strong>deferred</strong>в директиве<strong>listen</strong>в конфигурации<strong>Nginx</strong>), то управление приложению из вызова<strong>accept()</strong>возвращается, только когда пришли первые данные. Иначе - сразу после того, как произошел<strong>tcp handshake</strong>.</p>
5
<p><strong>Accept()</strong>возвращает файл-дескриптор нового сокета - сокета соединения. Это два разных сокета: первый (слушающий) имеет единственное состояние<strong>TCP_LISTEN</strong>, второй - все состояния, кроме<strong>TCP_LISTEN</strong>.</p>
5
<p><strong>Accept()</strong>возвращает файл-дескриптор нового сокета - сокета соединения. Это два разных сокета: первый (слушающий) имеет единственное состояние<strong>TCP_LISTEN</strong>, второй - все состояния, кроме<strong>TCP_LISTEN</strong>.</p>
6
<p>Все подключения до момента<strong>accept()</strong>помещаются в очередь, именуемую<strong>backlog</strong>. Очередь привязана к слушающему сокету, при этом в параметрах<strong>listen()</strong>указывается максимальная длина этой очереди -<strong>backlog size</strong>. При переполнении очереди соединение сразу отбрасывается (при<strong>proxy_pass</strong>в<strong>Nginx</strong>, например, получим ошибку 502). Очень часто разные приложения-сервера выносят эту настройку в свою конфигурацию.</p>
6
<p>Все подключения до момента<strong>accept()</strong>помещаются в очередь, именуемую<strong>backlog</strong>. Очередь привязана к слушающему сокету, при этом в параметрах<strong>listen()</strong>указывается максимальная длина этой очереди -<strong>backlog size</strong>. При переполнении очереди соединение сразу отбрасывается (при<strong>proxy_pass</strong>в<strong>Nginx</strong>, например, получим ошибку 502). Очень часто разные приложения-сервера выносят эту настройку в свою конфигурацию.</p>
7
<p>Как сказал бы Тони Роббинс будь он админом, а не коучем:</p>
7
<p>Как сказал бы Тони Роббинс будь он админом, а не коучем:</p>
8
<p><em>"если приложение перестаёт accept()'ить соединения, то растёт очередь в беклоге".</em></p>
8
<p><em>"если приложение перестаёт accept()'ить соединения, то растёт очередь в беклоге".</em></p>
9
<h2>Это была преамбула, а теперь фабула.</h2>
9
<h2>Это была преамбула, а теперь фабула.</h2>
10
<p>Долгое время админы, пришедшие в Linux с<strong>FreeBSD</strong>плакали, что нет возможности мониторить очередь сокета, вспоминая netstat -L, которого нет в Linux. Объяснялось это весьма мутной документацией netstat в этой части:</p>
10
<p>Долгое время админы, пришедшие в Linux с<strong>FreeBSD</strong>плакали, что нет возможности мониторить очередь сокета, вспоминая netstat -L, которого нет в Linux. Объяснялось это весьма мутной документацией netstat в этой части:</p>
11
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.<p>Я решил посмотреть, что делает ss -ln</p>
11
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.<p>Я решил посмотреть, что делает ss -ln</p>
12
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},<p>Ага, информацию ss берёт из ядра через<strong>netlink API</strong>. Поиск по ключевому слову NETLINK_SOCK_DIAG приводит нас в ман<strong>sock_diag(7)</strong>, в середине которого встречаем искомое:</p>
12
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},<p>Ага, информацию ss берёт из ядра через<strong>netlink API</strong>. Поиск по ключевому слову NETLINK_SOCK_DIAG приводит нас в ман<strong>sock_diag(7)</strong>, в середине которого встречаем искомое:</p>
13
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.<p><strong>В переводе на русский получается следующее:</strong>Если сокет слушающий, то<strong>Recv-Q</strong>обозначает длину очереди в соединениях, а<strong>Send-Q</strong>обозначает<strong>backlog_size</strong>, указанный в<strong>listen()</strong>.</p>
13
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.<p><strong>В переводе на русский получается следующее:</strong>Если сокет слушающий, то<strong>Recv-Q</strong>обозначает длину очереди в соединениях, а<strong>Send-Q</strong>обозначает<strong>backlog_size</strong>, указанный в<strong>listen()</strong>.</p>
14
<p>Если сокет - активный сокет соединения, то<strong>Recv-Q</strong>обозначает количество принятых байт, но не прочитанных приложением. А<strong>Send-Q</strong>- размер доступного пространства для отправки.</p>
14
<p>Если сокет - активный сокет соединения, то<strong>Recv-Q</strong>обозначает количество принятых байт, но не прочитанных приложением. А<strong>Send-Q</strong>- размер доступного пространства для отправки.</p>
15
<p>Подтверждается это кусками кода из ядра net/ipv4/tcp_diag.c:</p>
15
<p>Подтверждается это кусками кода из ядра net/ipv4/tcp_diag.c:</p>
16
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; }<p>net/unix/diag.c:</p>
16
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; }<p>net/unix/diag.c:</p>
17
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); }<p>Итого, ss -ln - то, что вам нужно при диагностике подключений к серверу и теперь вы знаете, как интерпретировать его вывод.</p>
17
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); }<p>Итого, ss -ln - то, что вам нужно при диагностике подключений к серверу и теперь вы знаете, как интерпретировать его вывод.</p>
18
<p><em>Есть вопрос? Напишите в комментариях!</em></p>
18
<p><em>Есть вопрос? Напишите в комментариях!</em></p>
19
19