Я написал простой многопоточный игровой сервер на 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
Одеяло - это плохая политика. Но все же он перехватит любое исключение. Вы знаете, что это ошибка IOError. Разберись с этим. Если возникает что-то еще, выясните, почему, и обработайте это соответствующим образом. Вы не хотите маскировать такие ошибки, как деление на ноль или нехватка памяти.
Если вы используете модуль сокета Python, вы не получите исключение IOError: вы получите исключение socket.error.
Вы не получите IOError. socket.error не имеет атрибута errno для сломанного канала - этот код вызовет AttributeError.
Вы не получите IOError с errno == EPIPE для исключений сломанных трубных сокетов, вы получите socket.error, поэтому нет смысла проверять его в обработчике исключений IOError. У вас 2 голоса за (все еще) плохой ответ. Возможно, вам стоит проголосовать за мой ответ :)
На этом этапе спрашивающий должен знать, как использовать оператор try :.
Согласитесь, что теперь у спрашивающего должно быть какое-то представление о том, что делать. Тем не менее, ваш фрагмент кода по-прежнему неверен, и спрашивающий может последовать вашему примеру. Это не сломает его код, просто проверка EPIPE в обработчике IOError бесполезна.
@mhawke: ты все еще прав. Оба раза. Однако сложно придумать пример стандартных ошибок ОС (с errno) и других ошибок (без errno). Я думаю, что важно иметь аккуратный образец кода - я не пишу их приложение для них.
Общие исключения обычно могут быть плохой политикой, но в данном случае это не так. Это более или менее то, для чего нужны исключения. Почти всегда лучше использовать операторы if для проверки того, что введенные вами входные данные и вызовы не будут вызывать ошибок, потому что это не испортит вашу отладку. Однако сокеты имеют ресурсы более низкого уровня, поэтому к ним можно легко получить доступ с помощью модуля сокета python. Поскольку мы не можем получить доступ к каналу и правильно обработать его на нижнем уровне, можно использовать исключения.
SIGPIPE (хотя, может быть, вы имеете в виду EPIPE?) Возникает на сокетах, когда вы закрываете сокет, а затем отправляете на него данные. Простое решение - не отключать сокет перед попыткой отправить ему данные. Это также может произойти с каналами, но это не похоже на то, что вы испытываете, поскольку это сетевой сервер.
Вы также можете просто применить бандаж для перехвата исключения в каком-либо обработчике верхнего уровня в каждом потоке.
Конечно, если бы вы использовали Скрученный вместо создания нового потока для каждого клиентского соединения, у вас, вероятно, не было бы этой проблемы. Действительно сложно (возможно, невозможно, в зависимости от вашего приложения) правильно упорядочить операции закрытия и записи, если несколько потоков работают с одним и тем же каналом ввода-вывода.
Мой ответ очень близок к ответу С.Лотта, за исключением того, что я был бы еще более конкретным:
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. Таким образом, вы не будете пытаться обрабатывать ошибку разрешений или что-то еще, для чего вы не готовы.
Errno будет 32, а не 23.
Я должен был пояснить, что имел в виду «23» в качестве заполнителя. Действительно? 32? Я был ближе, чем мог предположить. :-)
Предполагая, что вы используете стандартный модуль сокета, вы должны уловить исключение 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 отлично подходит для такого рода вещей, однако похоже, что вы уже написали довольно много кода.
Странно выглядит if isinstance(e.args, tuple):. Кто-нибудь может это объяснить?
Это означает, что "e.args - это кортеж?"
Почему EPIPE не всегда возникает при первой записи? При каком условии возникает EPIPE?
Я сталкиваюсь с тем же вопросом. Но в следующий раз я отправлю тот же код, он просто работает. Первый раз сломалось:
$ packet_write_wait: Connection to 10.. port 22: Broken pipe
Второй раз работает:
[1] Done nohup python -u add_asc_dec.py > add2.log 2>&1
Я предполагаю, что причина может быть в текущей серверной среде.
Если я попробую: #something except: #anything, будет ли он что-нибудь улавливать, а не только ошибки ввода-вывода?