在 C 中处理这个问题的标准方法是循环EINTR
. 而且,虽然这在 Python 中不应该是必需的,但确实如此。
您的代码非常接近处理此问题的惯用方式,除了两件事:
- 您不想忽略所有错误,只需
EINTR
.
- 以这种方式忽略错误后,您不能
yield data
这样做,因为您将重新生成前一个数据包(如果有)或引发 a 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 扩展)。但不幸的是,它有错误。发现并修复的最新案例集在issue 9867和issue 12268中。
即使他们最终掌握了一切,这也只有在您可以依赖足够新的 Python 版本时才会有所帮助。鉴于您使用的是 2.6 之前的样式except
语法,并且最新的修复进入了一些 2.7.x 和 3.2.x 错误修复版本,这可能对您不起作用。
还有其他方法可以解决这个问题,但它们更复杂,更不便携。例如,您可以将阻塞替换recv
为阻塞pselect
和非阻塞recv
,将 apipe
与套接字一起添加到 fd 集中,将所有信号处理程序替换为仅写入(一个字节)到该管道的函数,然后移动将实际的信号处理代码放入事件循环中。然后,在某些平台上,您将永远无法获得EINTR
. 但这可能不是您想在 Python 中采用的方法。