Как обработать сломанную трубу (SIGPIPE) в Python?
-
05-07-2019 - |
Вопрос
Я написал простой многопоточный игровой сервер на python, который создает новый поток для каждого клиентского соединения. Я обнаружил, что время от времени сервер падает из-за ошибки сломанной трубы / SIGPIPE. Я почти уверен, что это происходит, когда программа пытается отправить ответ клиенту, которого больше нет.
Какой хороший способ справиться с этим? Мое предпочтительное разрешение будет просто закрывать соединение на стороне сервера с клиентом и двигаться дальше, а не выходить из всей программы.
PS: Этот вопрос / ответ связан с проблема в общем виде; как конкретно это решить?
Решение
Прочитайте инструкцию try:
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. Таким образом, вы не будете пытаться обработать ошибку прав доступа или что-то еще, к чему вы не готовы.