104,“连接被对等方重置”的套接字错误,或者何时关闭套接字会导致RST而不是FIN?

40
我们正在同时开发Python Web服务和客户端网站。当客户端向服务发送HTTP请求时,一个调用在read函数中持续地引发socket.py中的socket.error:
(104, 'Connection reset by peer')
我使用wireshark监听时,“良好”和“不良”的响应看起来非常相似:
  • 由于OAuth头的大小,请求被拆分为两个数据包。服务对两个数据包都做出ACK响应。
  • 服务发送响应,每个标头一个数据包(HTTP / 1.0 200 OK,然后是Date头等)。客户端针对每个响应都会回复ACK。
  • (良好的请求)服务器发送FIN,ACK。客户端响应FIN,ACK。服务器响应ACK。
  • (不良请求)服务器发送RST,ACK,客户端没有发送TCP响应,客户端抛出socket.error错误。
Web服务和客户端均在运行Gentoo Linux x86-64操作系统上,使用glibc-2.6.1。我们在同一虚拟环境中使用Python 2.5.2。
客户端是一个Django 1.0.2应用程序,它使用httplib2 0.4.0进行请求。我们使用OAuth签名算法进行请求签名,其中OAuth令牌始终设置为空字符串。
服务正在运行Werkzeug 0.3.1,该工具使用Python的wsgiref.simple_server。 我使用wsgiref.validator验证了WSGI应用程序,没有发现问题。
看起来这个问题应该很容易调试,但是当我追踪服务端的良好请求时,它看起来与不良请求一样,在socket._socketobject.close()函数中将委托方法转换为虚拟方法。 当send或sendto(记不清了)方法关闭时,发送FIN或RST,并开始处理客户端。
“连接被对等方重置”似乎把责任归咎于服务端,但我也不信任httplib2。 客户端是否有错?

** 进一步调试 - 似乎是运行在Linux上的服务器 **

我有一台 MacBook,所以我尝试在其中一台电脑上运行服务,在另一台电脑上运行客户端网站。Linux 客户端呼叫 OS X 服务器时没有出现错误(FIN ACK)。但是,OS X 客户端呼叫运行在 Linux 上的服务时出现了问题(RST ACK,并且出现了 (54, 'Connection reset by peer') 的错误)。因此,看起来问题出现在运行在 Linux 上的服务上。它是否是 x86_64?是不是有缺陷的 glibc?是 wsgiref 的问题?我们还在继续寻找答案...

** 进一步测试 - wsgiref 看起来不稳定 **

我们已经使用 Apache 和 mod_wsgi 进入生产环境,连接重置的问题已经解决。请参见我的下面的答案,但我的建议是记录连接重置并进行重试。这将使您的服务器在开发模式下正常运行,并在生产环境中牢固可靠。


1
问题确实是为什么服务器发送 RST 请求。客户端必须重置连接并通知“对等方重置连接”消息。所以我认为您走在了正确的轨道上。 - Igal Serban
4个回答

28

我也遇到过这个问题。请参见Python“Connection Reset By Peer”问题

你很可能遇到了基于Python全局解释器锁的小时间问题。

你可以通过策略性地放置time.sleep(0.01)来纠正这个问题(有时候是有效的)。

"放在哪里?"你问。 没有明确答案,但思路是在客户端请求的周围提供更好的线程并发性。 尝试将其放置在请求之前,以便重置GIL并使Python解释器清除任何挂起的线程。


1
在链接中似乎问题出在在同一个进程中运行服务器和客户端。因此它们受制于GIL。 - Igal Serban
是的,但是...即使在不同的客户端服务器主机上,您看到的似乎是相同的连接重置。我仍然认为您应该在此处进行一些睡眠操作,以查看线程调度更改是否有所帮助。 - S.Lott

11

不要在生产环境中使用wsgiref。请使用Apache和mod_wsgi,或者其他替代方案。

我们继续看到这些连接重置,有时很频繁,使用wsgiref(werkzeug测试服务器使用的后端,可能还有其他类似Django测试服务器的后端)。我们的解决方案是记录错误,在循环中重试调用,并在十次失败后放弃。httplib2会尝试两次,但我们需要更多尝试。它们似乎也会一起出现 - 添加1秒的延迟可能会清除问题。

当我们向本地开发社区寻求帮助时,有人证实他们在wsgiref中看到了很多连接重置,但这些问题在生产服务器上消失了。这里存在一个错误,但很难找到它。

我们从未见过通过Apache和mod_wsgi运行时出现连接重置。我不知道他们做了什么不同的事情(也许只是掩盖了它们),但它们并没有出现。


3

通常情况下,如果您进行一个不会停留的关闭操作(即堆栈可以丢弃尚未发送和确认的数据),则会收到RST信号;如果您允许关闭操作停留(即关闭等待传输中的数据被确认),则会收到正常的FIN信号。

也许您只需要设置套接字以等待关闭操作完成,从而消除套接字上的非停留式关闭和ACK到达之间的竞争条件?


2
我遇到了同样的问题,使用python-requests客户端上传大文件到nginx+uwsgi后端时出现问题。最终原因是后端对于上传的最大文件大小限制比客户端试图发送的要低。由于这个限制实际上是由nginx施加的,所以错误从未在我们的uwsgi日志中显示。将nginx中的限制提高解决了错误。

天啊,在到处搜索之后,我发现“Content-Length”可能是这个错误的根本原因。 - amir jj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接