python socket recv()和信号

yee*_*379 6 python sockets signals alarm

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

问题是在recv()循环期间,出现的情况SIGALRM似乎提高了

socket.error: [Errno 4] Interrupted system call
Run Code Online (Sandbox Code Playgroud)

因此终止了该计划.

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

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
Run Code Online (Sandbox Code Playgroud)

aba*_*ert 9

在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
Run Code Online (Sandbox Code Playgroud)

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

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

像大多数脚本语言一样,Python应该在EINTR内部包装所有的prone调用,所以你不应该考虑这个(除非你使用的是第三方C扩展).但不幸的是,它有错误.找到并修复的最新案例集发布问题9867问题12268中.

即使他们最终抓住了所有东西,只有依靠足够新版本的Python才有用.鉴于您使用的是2.6之前的except语法,并且最新的修补程序进入了一些2.7.x和3.2.x版本的修复程序,这可能对您不起作用.


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