Socket backlog
Преамбула: как сервер принимает подключения?
Сервер делает так:
На блокирующих (по-умолчанию) сокетах приложение-сервер висит в 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 плакали, что нет возможности мониторить очередь сокета, вспоминая
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.
Я решил посмотреть, что делает
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. Поиск по ключевому слову
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); }
Итого,
Есть вопрос? Напишите в комментариях!