Как обработать сломанную трубу (SIGPIPE) в Python?
Я написал простой многопоточный игровой сервер на python, который создает новый поток для каждого клиентского соединения. Я обнаружил, что время от времени сервер падает из-за ошибки сломанной трубы /SIGPIPE. Я почти уверен, что это происходит, когда программа пытается отправить ответ клиенту, которого больше нет.
Какой хороший способ справиться с этим? Мое предпочтительное разрешение будет просто закрывать соединение на стороне сервера с клиентом и двигаться дальше, а не выходить из всей программы.
PS: этот вопрос / ответ имеет дело с проблемой в общем виде; как конкретно это решить?
5 ответов
Читайте на пробу: заявление.
try:
# do something
except socket.error, e:
# A socket error
except IOError, e:
if e.errno == errno.EPIPE:
# EPIPE error
else:
# Other error
Предполагая, что вы используете стандартный модуль сокета, вы должны поймать socket.error: (32, 'Broken pipe')
исключение (не IOError, как предлагали другие). Это будет затронуто в случае, который вы описали, т.е. отправка / запись в сокет, для которого удаленная сторона отключилась.
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
Обратите внимание, что это исключение не всегда будет вызываться при первой записи в закрытый сокет - чаще при второй записи (если только число байтов, записанных в первой записи, не превышает размер буфера сокета). Вы должны помнить об этом, если ваше приложение думает, что удаленный конец получил данные с первой записи, когда он, возможно, уже отключился.
Вы можете уменьшить частоту (но не полностью устранить), используя select.select()
(или же poll
). Проверьте, готовы ли данные для чтения с однорангового узла, прежде чем пытаться выполнить запись. Если select
сообщает, что есть данные, доступные для чтения из однорангового сокета, прочитайте его, используя socket.recv()
, Если это возвращает пустую строку, удаленный узел закрыл соединение. Поскольку здесь все еще есть состояние гонки, вам все равно нужно поймать и обработать исключение.
Twisted отлично подходит для такого рода вещей, однако, похоже, вы уже написали немало кода.
SIGPIPE
(хотя я думаю, может быть, вы имеете в виду EPIPE
?) происходит на сокетах, когда вы закрываете сокет и затем отправляете в него данные. Простое решение - не закрывать сокет перед тем, как отправлять данные. Это также может происходить на каналах, но это не похоже на то, что вы испытываете, поскольку это сетевой сервер.
Вы также можете просто применить перехват для перехвата исключения в некотором обработчике верхнего уровня в каждом потоке.
Конечно, если бы вы использовали Twisted вместо того, чтобы создавать новый поток для каждого клиентского соединения, у вас, вероятно, не возникло бы этой проблемы. Действительно трудно (возможно, невозможно, в зависимости от вашего приложения) правильно упорядочить операции закрытия и записи, если несколько потоков работают с одним и тем же каналом ввода-вывода.
Я сталкиваюсь с тем же вопросом. Но я отправлю тот же код в следующий раз, он просто работает. Первый раз это сломалось:
$ packet_write_wait: Connection to 10.. port 22: Broken pipe
Второй раз это работает:
[1] Done nohup python -u add_asc_dec.py > add2.log 2>&1
Я думаю, причина может быть в текущей серверной среде.
Мой ответ очень близок к ответу С. Лотта, за исключением того, что я бы сказал более конкретно:
try:
# do something
except IOError, e:
# ooops, check the attributes of e to see precisely what happened.
if e.errno != 23:
# I don't know how to handle this
raise
где "23" - это номер ошибки, который вы получаете из EPIPE. Таким образом, вы не будете пытаться обработать ошибку прав доступа или что-то еще, к чему вы не готовы.