偶尔的 ECONNRESET

2026-05-17 1 阅读 zdw
博客 - git - 桌面 - 联系方式 偶尔的 ECONNRESET(第 1/2 部分) 2026-05-05 两个服务在同一台机器上运行。其中一个打开一个绑定到本地主机的侦听 TCP 套接字,另一个连接到该套接字。他们交换数据。时不时地,启动连接的服务在从套接字读取数据时会收到 ECONNRESET——但日志中没有显示其他错误,没有崩溃,什么也没有。这是怎么回事? “实验室”中的重现器 tcpdump 看到的内容 strace ./server 看到的内容 strace ./client --spam 看到的内容 第一个假设 现实场景 后续步骤 继续第 2 部分。 “实验室”中的重现器让我们从“服务器”开始,即打开侦听套接字的服务。下面的程序就是这样做的:创建一个新的 TCP 套接字,等待连接,为每个请求分叉到一个新进程。不过,并没有太多的“请求”:服务器在连接时只是将 600'000 x 字节转储到客户端。数字 600'000 具有一定的意义:它需要足够大才能触发我想要展示的行为。例如,600 字节可能行不通。 server.c 现在到客户端:它连接到我们的服务器正在等待的本地主机上的端口 8125,然后调用 recv() 直到 EOF 或错误。我们很快就会看到 --spam 标志。 client.c 另外:Makefile 让我们运行两个程序: [terminal1]$ ./server [terminal2]$ ./client 读取 600000 字节,最终返回值为 0,errno 为 0 没什么了不起的。但是让我们使用 --spam 标志: $ ./client --spam 读取 600000 字节,最终返回值为 -1,errno 为 104 连接被对等重置 $ ./client --spam 读取 256000 字节,最终返回值为 -1,errno 为 104 连接被对等重置 $ ./client --spam 读取 351232 字节,最终返回值为 -1,errno 为104 连接被对等方重置 $ ./client --spam 读取 351232 字节,最终返回值为 -1,errno 为 104 连接被对等方重置 $ ./client --spam 读取 351232 字节,最终返回值为 -1,errno 为 104 连接被对等方重置 $ ./client --spam 读取 256000 字节,最终返回值为 -1,errno 为 104连接被对等重置 $ ./client --spam 读取 600000 字节,最终返回值为 -1,errno 为 104 连接被对等重置 --spam 导致客户端在尝试接收数据之前首先向服务器发送一些数据。显然,这会导致连接在某个时刻中断:客户端的 recv() 看到返回值为 -1 ,并且 errno 被设置为 104 = Connection Reset by Peer 。 tcpdump 看到什么 首先,什么是“在线”?好的。所以实际上有一个TCP RST。也可能是我的编程错误或误解。 strace ./server 看到的内容 RST 来自服务器,所以让我们附加 strace 并看看我们得到什么: 19:59:03.420432accept(3, NULL, NULL) = 4 19:59:05.652715clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe43484fa10) = 239546 [pid 239546] 19:59:05.652831 ... [pid 239546] 19:59:05.652959 mmap(NULL, 602112, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <未完成 ...> [pid 239546] 19:59:05.652980 <... mmap 已恢复>) = 0x7fe43456d000 [pid 239546] 19:59:05.653235 sendto(4, “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”...,600000,0,NULL,0)= 600000 [pid 239546] 19:59:05.653474 close(4)= 0 [pid 239546] 19:59:05.653553 exit_group(0)=? [pid 239546] 19:59:05.653667 +++ 退出并显示 0 +++ 没有崩溃。它分叉并使用 sendto() 将所有数据转储到客户端。然后就退出了。另请注意,sendto() 返回了完整的 600000 ,因此从该程序的角度来看,“所有数据均已发送”(显然有一个脚注,正如联机帮助页所解释的:“成功完成对 sendto() 的调用并不能保证消息的传递。返回值 -1 表示仅本地检测到的错误。”)。事实上,无论你在客户端是否使用--spam,这里都没有区别。 strace ./client --spam 看到什么 19:59:05.652518 connect(3, {sa_family=AF_INET, sin_port=htons(8125), sin_addr=inet_addr("127.0.0.1")}, 16) = 0 19:59:05.652649 sendto(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 100, 0, NULL, 0) = 100 19:59:05.652805 接收来自(3, “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”..., 4096, 0, NULL, NULL) = 4096 19:59:05.653382 recvfrom(3, “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”..., 4096, 0, NULL, NULL) = 4096 ... 19:59:05.654440 recvfrom(3, “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”..., 4096, 0, NULL, NULL) = 4096 19:59:05.654473 recvfrom(3, “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”..., 4096, 0, NULL, NULL) = 1024 19:59:05.654506 recvfrom(3, 0x55d23c5b5010, 4096, 0, NULL, NULL) = -1 ECONNRESET (连接由对等方重置) 19:59:05.654575 write(1, "读取 128000 字节,最终返回 "..., 60) = 60 19:59:05.654694 write(4, "连接由对等方重置对等\n", 25)