ESTAB 状态下的 Recv-Q Send-Q 分别表示内核协议栈的发送缓冲区和接受缓冲区中保存的内容字节数。
在 Linux 上,TCP 的三次握手在内核里完成。内核通过一个半连接和已连接 2 个队列来实现。
已连接队列的大小由 net.core.somaxconn 和 int listen(int sockfd, int backlog) 传入的 backlog 较小值来决定。对于 LISTEN 状态的 Send-Q 表示的就是这个队列的大小。
默认的内核参数 net.core.somaxconn 通常都是 128.
sysctl -a | grep somaxconn
在 Linux localhost.localdomain 2.6.32-696.13.2.el6.x86_64 #1 SMP Thu Oct 5 21:22:16 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux CentOS release 6.9 (Final) 上做使用 ipython 做一组对比测试,说明是由 net.core.somaxconn 和 int listen(int sockfd, int backlog) 传入的 backlog 当中的较小者决定。
import socket
serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
serv.bind(("0.0.0.0", 7777))
serv.listen(134)
如果仍由内核完成3次握手,应用层不调用 accept 从已连接队列中取走连接,那么已连接队列最终将满,容不下更多的连接。
LISTEN 状态的 Recv-Q 表示的就是等待被 accpet 取走的连接数。
假如有 5 个客户端发起连接。
import socket
client_lists = []
for i in range(5):
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
client.connect(("192.168.1.12", 7777))
client_lists.append(client)
服务器上看到的 Recv-Q 将是 5.
Recv-Q 的值也有一个上限。在 Linux 上的行为,Recv-Q 的上限为 Send-Q + 1.
达到上限之后的行为,跟这个 net.ipv4.tcp_abort_on_overflow 内核参数有关。默认的,net.ipv4.tcp_abort_on_overflow 的值是 0. 表示当已连接队列满,此时如果有新的连接请求 SYN 进来,服务器一样的回复 SYN-ACK,像正常的 3 次握手一样,服务器进入的是 SYN-RECV 状态。
此后客户端会进行 3 次握手的最后一个步骤,发送 ACK 给服务器。服务器内核此时不断的重复握手的第二个步骤,发送 SYN-ACK 以等待连接队列能有空间。
5 次重复之后(是否由 tcp_synack_retries 参数决定待确认), 内核若不能进入 ESTAB,将直接丢弃改链接,使用 ss 已经没有该链接的信息。
如果 net.ipv4.tcp_abort_on_overflow 的值为 1.
sysctl -w net.ipv4.tcp_abort_on_overflow=1
那么服务器的将对 3 次握手的最后一次 ACK 进行 RST. 以下为重复 3 次的结果。
客户端使用原生 C 语言进行。
#include
#include
#include
#include
#include
#include
//如果是 C 语言,sockaddr, sockaddr_in 都是结构体标记,需要使用 struct 前缀才是类型。
//使用 memset 需要包含 string.h,否则会警告: warning: incompatible implicit declaration of built-in function ‘memset’
int main()
{
struct sockaddr_in serv_addr, client_addr;
memset((void*)&serv_addr, 0 , sizeof(serv_addr));
memset((void*)&client_addr, 0, sizeof(client_addr));
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(7777);
//serv_addr.sin_addr.s_addr = INADDR_ANY;
inet_pton(AF_INET, "192.168.1.12", &serv_addr.sin_addr);
int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
printf("ret is %d\n", ret);
if(ret == 0)
{
printf("connect success!\n");
socklen_t len = sizeof(client_addr);
getsockname(fd, (struct sockaddr*)&client_addr, &len);
char buff[64] = {0};
inet_ntop(AF_INET, &client_addr.sin_addr, buff, sizeof(client_addr));
printf("local name %s:%d\n", buff, ntohs(client_addr.sin_port));
sleep(300);
}
else
{
perror("connect failed");
}
return 0;
}
比较奇怪的地方是,虽然从抓包的情况来看,每次的数据包结果一样。但是,客户端有的时候 connect 是能返回成功的。猜想 connect 在收到 RST 之前返回了可能就成功了,在收到 RST 之后才返回就失败了。重复多次依然有的时候 connect 返回成功,有的时候返回失败。
本次测试环境为:Linux centos02 2.6.32-573.el6.x86_64 #1 SMP Thu Jul 23 15:44:03 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux CentOS release 6.7 (Final).