如何在Python中处理破裂管道(SIGPIPE)?

57 投票
5 回答
103939 浏览
提问于 2025-04-11 09:29

我写了一个简单的多线程游戏服务器,用Python编写,每当有客户端连接时,就会为其创建一个新的线程。不过,我发现服务器有时候会崩溃,出现“broken-pipe/SIGPIPE”错误。我觉得这个问题发生在程序试图向一个已经不在的客户端发送响应时。

有什么好的方法来解决这个问题吗?我希望的解决办法是直接关闭服务器与客户端的连接,然后继续运行,而不是让整个程序退出。

附注:这个问题/答案以一种通用的方式处理了这个问题;我应该具体怎么解决呢?

5 个回答

3

SIGPIPE(不过我觉得你可能是想说 EPIPE?)是在你关闭一个套接字(socket)后再往里面发送数据时出现的错误。简单的解决办法就是在尝试发送数据之前不要关闭这个套接字。这个问题也可能发生在管道(pipes)上,但听起来你遇到的情况不是这样,因为你是在做网络服务器。

你也可以在每个线程的顶层处理程序中捕获这个异常,算是一种临时解决办法。

当然,如果你使用的是 Twisted 而不是为每个客户端连接新开一个线程,你可能就不会遇到这个问题。如果多个线程同时处理同一个输入输出通道,正确地安排关闭和写入操作的顺序是非常困难的(可能甚至是不可能的,这取决于你的应用程序)。

59

假设你在使用标准的socket模块,你应该捕捉到socket.error: (32, 'Broken pipe')这个错误(而不是像其他人建议的那样捕捉IOError)。这个错误会在你描述的情况下出现,也就是当你尝试向一个已经断开的socket发送或写入数据时。

import socket, errno, time

# setup socket to listen for incoming connections
s = socket.socket()
s.bind(('localhost', 1234))
s.listen(1)
remote, address = s.accept()

print "Got connection from: ", address

while 1:
    try:
        remote.send("message to peer\n")
        time.sleep(1)
    except socket.error, e:
        if isinstance(e.args, tuple):
            print "errno is %d" % e[0]
            if e[0] == errno.EPIPE:
               # remote peer disconnected
               print "Detected remote disconnect"
            else:
               # determine and handle different error
               pass
        else:
            print "socket error ", e
        remote.close()
        break
    except IOError, e:
        # Hmmm, Can IOError actually be raised by the socket module?
        print "Got IOError: ", e
        break

需要注意的是,这个错误并不总是在你第一次写入一个关闭的socket时就会出现,通常是在第二次写入的时候才会出现(除非你第一次写入的数据量超过了socket的缓冲区大小)。你要记住这一点,因为你的应用可能会认为远端已经接收到了第一次写入的数据,但实际上它可能已经断开了连接。

你可以通过使用select.select()(或者poll)来减少这种情况的发生(但不能完全消除)。在尝试写入之前,先检查一下对方是否有数据可以读取。如果select报告说对方socket有数据可读,那就用socket.recv()来读取。如果返回的是空字符串,说明远端已经关闭了连接。因为这里仍然存在竞争条件,所以你仍然需要捕捉并处理这个错误。

Twisted在处理这种情况时非常好,不过听起来你已经写了不少代码了。

41

了解一下try:语句。

try:
    # do something
except socket.error, e:
    # A socket error
except IOError, e:
    if e.errno == errno.EPIPE:
        # EPIPE error
    else:
        # Other error

撰写回答