正在listen的socket收到connect后,没有accept时,listen链路recvQ非0,此时新socket已经建链,而且是ESTABLISHED状态,抓包可以确认tcp自动完成3次握手的:
tcp 1 0 10.47.160.67:8020 0.0.0.0:* LISTEN
tcp 0 0 10.47.160.67:8020 10.47.160.95:39057 ESTABLISHED
这时客户端向新建链路发送消息后,会看到链路的recvQ有数据等待被取走:
tcp 1 0 10.47.160.67:8020 0.0.0.0:* LISTEN
tcp 604 0 10.47.160.67:8020 10.47.160.95:39057 ESTABLISHED
这时服务端accept()只是从listen队列里取出一个socket:
tcp 0 0 10.47.160.67:8020 0.0.0.0:* LISTEN
tcp 604 0 10.47.160.67:8020 10.47.160.95:39057 ESTABLISHED
服务端新链接执行recv()就可以从链路读取指定长度数据:
tcp 0 0 10.47.160.67:8020 0.0.0.0:* LISTEN
tcp 504 0 10.47.160.67:8020 10.47.160.95:39057 ESTABLISHED
如果服务端没有及时accept,监听socket上会积压,LISTEN链路上recvQ队列的积压是tcp的已建链队列数据,又称accept队列,等待服务端accept取走;SYN_RECV链接是半链接状态,半链接队列、accept队列的长度计算见下面伪代码。
tcp 11 0 10.47.160.67:8020 0.0.0.0:* LISTEN
tcp 0 0 10.47.160.67:8020 10.47.160.95:56733 SYN_RECV
如果此时客户端发送一个FIN包,recvQ多了一个字节,而且服务端状态变为CLOSE_WAIT;
tcp 0 0 10.47.160.67:8020 0.0.0.0:* LISTEN
tcp 505 0 10.47.160.67:8020 10.47.160.95:39057 CLOSE_WAIT
这时服务执行recv()读取全部数据后,数据被取走,但没有返回失败:
tcp 0 0 10.47.160.67:8020 0.0.0.0:* LISTEN
tcp 0 0 10.47.160.67:8020 10.47.160.95:39057 CLOSE_WAIT
这时服务端是close_wait,客户端是FIN_WAIT2,服务再次执行recv(),返回0,表示收到FIN包。
tcp 0 0 10.47.160.67:8020 0.0.0.0:* LISTEN
补充:如果发送方应用进程发送完数据后,立即发送一个RST包,这时协议栈里积压的数据会丢弃,接收方无法接收完整数据库,而是直接断链。
listen(backlog)
{
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned)backlog > somaxconn)
backlog = somaxconn;
inet_listen->inet_csk_listen_start(sk, backlog);
nr_table_entries = min( backlog, sysctl_max_syn_backlog);
nr_table_entries = max( nr_table_entries, 8);
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1); // nr_table_entries 是request sock queue 的长度
sk->sk_max_ack_backlog = backlog; //这里确定了accept queue的长度。
}
backlog为listen()的第二个参数;
sysctl_max_syn_backlog为系统配置net.ipv4.tcp_max_syn_backlog;
sock_net(sock->sk)->core.sysctl_somaxconn为系统配置net.core.somaxconn;