我相信您可以通过在操作系统级别调整 TCP 设置来摆脱挂起状态,但假设您的应用程序无法在专用(并且可由您维护)机器上运行,您应该寻求更通用的解决方案。
您询问:
是否只有在一段时间内没有通过套接字发送/接收数据时才可能超时
这正是socket.settimeout
(或传递给urllib2
)会给你的行为。与基于 SIGALRM 的超时(即使在缓慢的数据传输期间也会终止)相反,只有在定义的时间段内没有传输数据时才会发生传递给套接字的超时。如果在此期间已经传输了一些但不是所有数据,则调用socket.send
or应该返回部分计数,然后将使用后续调用来传输剩余数据。socket.recv
urllib2
话虽如此,如果您的 POST 调用将在多个send
调用中执行,并且任何(但不是第一个)调用会阻塞并超时而不发送任何数据,则它仍可能在上传过程中的某个地方终止。您给人的印象是您的应用程序无法正确处理它,但我认为它应该,因为它类似于强制终止进程或只是断开连接。
您是否测试并确认socket.settimeout
不能解决您的问题?或者您只是不确定行为是如何实现的?如果前者是正确的,请您提供更多细节吗?我很确定您只需设置超时是安全的,因为 python 只是使用行为如上所述的低级 BSD 套接字实现。为了给您更多参考,请查看setsockopt
手册页和SO_RCVTIMEO
/或SO_SNDTIMEO
选项。我希望socket.settimeout
完全使用这些功能和选项。
--- EDIT --- (提供一些测试代码)
因此,我能够获取Requests
模块并与urllib2
. recv
我已经运行了正在接收数据块的服务器,每次调用之间的间隔越来越长。正如预期的那样,当间隔达到指定的超时时间时,客户端超时。例子:
服务器
import socket
import time
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.bind(("localhost", 12346))
listener.listen(1)
sock,_ = listener.accept()
interval = 0.5
while 1:
interval += 1 # increase interval by 1 second
time.sleep(interval)
# Get 1MB but will be really limited by the buffer
data = sock.recv(1000000)
print interval, len(data)
if not data:
break
客户端 (请求模块)
import requests
data = "x"*100000000 # 100MB beefy chunk
requests.post("http://localhost:12346", data=data, timeout=4)
客户端 (urllib2 模块)
import urllib2
data = "x"*100000000 # 100MB beefy chunk
urllib2.urlopen("http://localhost:12346", data=data, timeout=4)
输出 (服务器)
> 1.5 522832
> 2.5 645816
> 3.5 646180
> 4.5 637832 <--- Here the client dies (4.5 seconds without data transfer)
> 5.5 294444
> 6.5 0
两个客户都提出了一个例外:
# urllib2
URLError: timeout('timed out',)
# Requests
Timeout: TimeoutError("HTTPConnectionPool(host='localhost', port=12346): Request timed out. (timeout=4)",)
一切都按预期工作!如果没有将超时作为参数传递,则对 的urllib2
反应也很好socket.setdefaulttimeout
,但Requests
没有。这并不奇怪,因为内部实现根本不需要使用默认值,并且可以根据传递的参数简单地覆盖它或使用非阻塞套接字。
我一直在使用以下方法运行它:
OSX 10.8.3
Python 2.7.2
Requests 1.1.0