关于套接字:如何在Python中使用Urllib2使用urlopen关闭超时的http POST?

How to shutdown a timed out http POST using urlopen by urllib2 in Python?

概观

我正在使用Python 2.7.1 urllib2包中的urlopen从Windows XP机器到远程Apache Web服务器(例如Mac OS X的内置Web共享)执行HTTP POST。发送的数据包含一些标识符,数据和校验和,如果发送了所有数据,则服务器以确认响应。数据中的校验和可用于检查一切是否按顺序到达。

问题

通常这很好用,但有时互联网连接不好,通常是因为发送数据的客户端使用wifi或3G连接。这导致互联网连接丢失一段任意时间。 urlopen包含一个超时选项,以确保它不会阻止您的程序,它可以继续。

这就是我想要的,但问题是urlopen不会阻止套接字继续发送超时发生时仍然必须发送的任何数据。我通过尝试向我的笔记本电脑发送大量数据来测试这个(我将在下面显示的代码),我会在两个show activity上看到网络活动,然后我会在笔记本电脑上停止无线,等待直到该功能超时,然后重新激活无线,然后数据传输将继续,但程序将不再监听响应。我甚至试图退出Python解释器,它仍然会发送数据,因此控制权以某种方式传递给Windows。

原因

超时(据我所知)的工作原理如下:
它会检查"空闲响应时间"
([Python-Dev]向urllib2添加套接字超时)
如果将超时设置为3,它将打开连接,启动计数器,然后尝试发送数据并等待响应,如果在接收响应之前的任何时刻计时器用完,则会调用超时异常。请注意,就超时计时器而言,发送数据似乎不算作"活动"。
(urllib2超时但没有关闭套接字连接)
(关闭urllib2连接)

显然它是在某个地方陈述当套接字被关闭/解除引用/垃圾收集时它调用它的'close'函数,它等待在关闭套接字之前发送所有数据。但是还有一个关闭功能,它应该立即停止套接字,防止发送更多数据。
(socket.shutdown vs socket.close)
(http://docs.python.org/library/socket.html#socket.socket.close)

我想要的是

我希望在发生超时时连接为"关闭"。否则,我的客户将无法判断数据是否已正确接收,并且可能会尝试再次发送。我宁愿直接杀死连接并稍后再试,知道数据(可能)没有成功发送(如果校验和不匹配,服务器可以识别这个)。

这是我用来测试它的代码的一部分。 try..except部分还没有像我期望的那样工作,任何帮助也有所值得赞赏。正如我之前所说,我希望程序在引发超时(或任何其他)异常时立即关闭套接字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from urllib import urlencode
from urllib2 import urlopen, HTTPError, URLError
import socket
import sys

class Uploader:
    def __init__(self):
        self.URL ="http://.../"
        self.data = urlencode({'fakerange':range(0,2000000,1)})
        print"Data Generated"

    def upload(self):
        try:
            f = urlopen(self.URL, self.data, timeout=10)
            returncode = f.read()
        except (URLError, HTTPError), msg:
            returncode = str(msg)
        except socket.error:
            returncode ="Socket Timeout!"
        else:
            returncode = 'Im here'

def main():
    upobj = Uploader()
    returncode = upobj.upload()

    if returncode == '100':
        print"Success!"
    else:
        print"Maybe a Fail"
        print returncode
    print"The End"

if __name__ == '__main__':
main()


您可以考虑使用与urllib2不同的API。 httplib有点不太愉快,但往往不是太糟糕。但是,它确实可以访问底层套接字对象。所以,你可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import httplib
import socket

def upload(host, path, data):
    conn = httplib.HTTPConnection(host, 80, True, 3)
    try:
        conn.request('POST', path, data)
        response = conn.getresponse()
        if response.status != 200:
            # maybe an HTTP error                                                                                    
            return response.status
        else:
            response_data = r.read()
            return response_data
    except socket.error:
        return"Socket Timeout!"
    finally:
        conn.sock.shutdown()
        conn.close()

def main():
    data = urlencode({'fakerange':range(0,2000000,1)})
    returncode = upload("www.server.com","/path/to/endpoint", data)

    ...

(免责声明:未经测试)

与urllib2相比,httplib确实有各种限制 - 例如,它不会自动处理重定向等内容。但是,如果您使用它来访问相对固定的API而不是从互联网上下载随机内容,它应该可以正常工作。

老实说,我可能自己也不愿意这样做;我通常满足于让操作系统处理TCP缓冲区但是它想要,即使它的方法并不总是完全最优的......


我找到了一些可能在这个帖子上帮助你的代码:

1
2
3
4
5
6
7
8
9
10
from urllib2 import urlopen
from threading import Timer
url ="http://www.python.org"
def handler(fh):
    fh.close()
    fh = urlopen(url)
    t = Timer(20.0, handler,[fh])
    t.start()
    data = fh.read()
    t.cancel()


事实证明,在正在上传的HTTPConnection上调用.sock.shutdown(socket.SHUT_RDWR)和.close()命令不会停止上载。它将继续在后台运行。在使用urllib2或httplib时,我不知道更可靠/直接的方法来从Python中终止连接。
最后,我们使用urllib2测试了上传而没有超时。这意味着在慢速连接上进行上传(POST)可能需要很长时间,但至少我们知道它是否有效。由于没有超时,urlopen可能会挂起,但我们已经测试了各种错误连接的可能性,并且在所有情况下,urlopen工作或在一段时间后返回错误。
这意味着我们至少会在客户端知道上传成功或失败,并且它不会在后台继续。


您可以使用multiprocessing生成辅助线程,然后在检测到超时时将其关闭(URLError异常,并显示消息"urlopen error time out")。

停止进程应足以关闭套接字。


如果调用socket.shutdown确实是切断超时数据的唯一方法,我认为你需要采取某种猴子修补方式。 urllib2并没有真正为你提供这种细粒度套接字控件的机会。

查看使用Python和urllib2的Source接口以获得一个好方法。