
突发偶发 ECONNRESET 错误背后实验室复现、场景分析与后续解决思路2026年5月5日同一台机器上运行的两个服务出现问题发起连接的服务读取数据时偶发 ECONNRESET 错误且日志无其他错误信息、无崩溃情况。下面我们来深入探究这一问题。实验室环境下的复现方法先从“服务器”打开监听套接字的服务开始有程序创建新的 TCP 套接字等待连接为每个请求派生新进程。连接建立后服务器向客户端发送 600000 个 x 字节的数据600000 这个数字需足够大才能触发想要展示的现象。相关代码文件有 [server.c](server.c) 。客户端连接到本地主机的 8125 端口调用 recv() 函数直到遇到 EOF 或错误。稍后会介绍 --spam 标志的作用相关代码文件有 [client.c](client.c) 还有 [Makefile](Makefile) 。运行这两个程序正常运行时无特别情况。但使用 --spam 标志时客户端的 recv() 函数返回值为 -1errno 被设置为 104即“连接被对端重置”。tcpdump 捕获的信息查看网络上实际传输的内容能看到有一个 TCP RST 包这说明问题可能并非编程错误或误解导致。strace ./server 的输出情况由于 RST 包由服务器发出用 strace 查看服务器系统调用情况。服务器派生新进程用 sendto() 函数将所有数据发送给客户端后退出sendto() 函数返回完整的 600000 字节无论客户端是否使用 --spam 标志情况都无区别。strace ./client --spam 的输出情况客户端不断调用 recvfrom() 函数直到其中一个调用返回 -1 并设置 ECONNRESET 错误这里也无异常情况。初步假设测试发现有些调用读取完整的 600000 字节有些返回其他值这可能是时间问题。事后看来延迟服务器中的 close() 调用是明显做法。在 serve_client() 函数中调用 close() 前添加 sleep(1) tcpdump 输出能明显看到一秒延迟。推测服务器在套接字上接收到传入数据但未读取调用 close() 时套接字处于“脏”状态所以发送 RST 包告知客户端并非所有数据都已被读取不过还未找到确切资料证实。实际应用场景[Gunicorn](https://gunicorn.org/) 运行 [Flask](https://flask.palletsprojects.com/en/stable/) 应用在 [Nginx](https://nginx.org/en/) 反向代理之后。有时 Nginx 会从 Gunicorn 收到 ECONNRESET 错误涉及云托管服务提供商、防火墙、大量路由器以及并行请求等因素还有 ioctl 和非阻塞套接字的影响。Nginx 通过两个系统调用将 HTTP 请求传递给 GunicornGunicorn 从套接字读取数据。但时不时地Gunicorn 只能收到第一个 writev() 调用的数据Gunicorn 或其内部应用程序只接收头部数据就正常工作不关心主体数据猜测这部分软件栈是“惰性”的。Gunicorn 发送数据并关闭套接字若有未读取的数据会导致发送 RST 包。解决方法是让 Python 应用程序对 HTTP 主体进行空操作确保从套接字中完全读取主体数据目前未再看到 ECONNRESET 错误但可能导致拒绝服务攻击风险Nginx 中的 client_max_body_size 配置项可能可避免这种情况。后续步骤验证 close() 调用是否真的是 TCP RST 包的原因并找到相关的确切资料这可能与 [RFC 1122](https://www.rfc-editor.org/rfc/rfc1122#page-88) 有关。找出 Python 端的问题所在是 Gunicorn、Flask 还是实际的 Flask 应用程序并向上游报告问题相关问题 [已有人报告](https://github.com/benoitc/gunicorn/pull/3546) 。请继续阅读[第 2 部分](/blog/postings/2026-05-17/0/POSTING-en.html)。