Python socket recv()和信号

6
我有一个简单(非线程)的脚本,监听一个套接字以获取数据,分析它并使用内部的 SIGALRM 在预定义的时间间隔内发送电子邮件。
问题是在 recv() 循环期间,SIGALRM 的发生似乎引发了一个……
socket.error: [Errno 4] Interrupted system call

因此终止程序。

我可以将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
1个回答

9
在C语言中处理这个问题的标准方法是循环处理 EINTR。虽然在Python中这不应该是必要的,但实际上是需要的。
你的代码非常接近处理这个问题的惯用方式,除了两件事:
  • 你不想忽略所有错误,只忽略 EINTR
  • 在忽略错误后,你不能再yield data,因为你会重新 yield 上一个数据包(如果有的话),或者引发一个 NameError(如果这是第一次通过循环)。
  • 所以:
    while True:
        try:
            data = s.recv(2048)
        except socket.error, e:
            if e.errno != errno.EINTR:
                raise
        else:
            yield data
    

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

    POSIX允许几乎任何系统调用在某些临时故障(包括被信号中断)时返回EINTR。许多POSIX平台都是这样做的。预期的应用程序行为是重试(如果您正在尝试阻塞调用)或返回循环(如果您正在使用级联触发反应器)。这篇博客文章提供了一个相当好的解释,解释了为什么POSIX以这种方式工作。(这是一种事后的辩解,绝对不是实际的理由...)还请参见glibc文档

    像大多数脚本语言一样,Python应该在内部包装所有易受EINTR影响的调用,因此您不必考虑这个问题(除非您使用第三方C扩展)。但不幸的是,它有漏洞。最近发现和修复的一组案例在问题9867问题12268中。

    即使他们最终捕获了所有问题,这只有在您可以依赖足够新的Python版本时才有帮助。考虑到您正在使用早于2.6版本的except语法,并且最新的修复程序已经发布到一些2.7.x和3.2.x的错误修复版本中,这对您可能不起作用。

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


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