python socket recv()和信号

时间:2013-04-18 23:11:42

标签: python sockets signals alarm

我有一个简单的(非线程)脚本,它在套接字上侦听数据,对其进行分析并使用内部SIGALRM来发送预定义计时器内部的电子邮件。

问题出在recv()循环期间,SIGALRM的出现似乎引发了

socket.error: [Errno 4] Interrupted system call

因此终止该计划。

我可以使用try / except块包装recv(),但我想知道在此期间我是否会丢失任何数据,或者缓冲区是否会阻止丢失。

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((host, port))
while True:
    try:
        data = s.recv(2048)
    except socket.error, e:
        pass
    yield data
s.close()
return

1 个答案:

答案 0 :(得分:8)

在C中处理此问题的标准方法是循环EINTR。而且,虽然在Python中不应该,但它是。

你的代码非常接近处理这个问题的惯用方法,除了两件事:

  • 您不想忽略所有错误,只需EINTR
  • 在忽略错误之后你不能yield data,因为你将重新产生前一个数据包(如果有的话)或者提出NameError(如果这是第一次)通过循环)。

所以:

while True:
    try:
        data = s.recv(2048)
    except socket.error, e:
        if e.errno != errno.EINTR:
            raise
    else:
        yield data

那么,你为什么要这样做?

POSIX允许几乎任何系统调用返回EINTR用于某些类型的临时故障 - 包括被信号中断。许多POSIX平台都是这样做的。预期的应用程序行为是重试(如果您正在尝试阻塞调用)或返回循环(如果您在级别触发的反应器内)。 This blog post给出了POSIX以这种方式工作的原因。 (这是事后的理由,绝对不是实际的理由......)另见the glibc documentation

与大多数脚本语言一样,Python应该在内部包含所有EINTR - 倾向调用,因此您不必考虑这一点(除非您使用的是第三方C扩展)。但不幸的是,它有错误。找到并修复的最新一组案例位于issue 9867issue 12268

即使他们最终抓住了所有内容,只有依靠足够新版本的Python才有用。鉴于您正在使用2.6之前的except语法,并且最新的修补程序进入了某些2.7.x和3.2.x版本的修正版,这可能不适合您。


还有其他方法可以解决这个问题,但它们更复杂,更不便携。例如,您可以使用阻止recv和非阻塞pselect替换阻止recv,将pipe与套接字一起添加到fd集中,替换所有您的信号处理程序具有只写入(一个字节)到该管道的函数,并将实际的信号处理代码移动到事件循环中。然后,在某些平台上,您永远不会得到EINTR。但这可能不是你想要用Python的方法。