Как обработать сломанный канал (SIGPIPE) в Python?

Я написал простой многопоточный игровой сервер на Python, который создает новый поток для каждого клиентского соединения. Я нахожу, что время от времени сервер падает из-за ошибки сломанной трубы / SIGPIPE. Я почти уверен, что это происходит, когда программа пытается отправить ответ клиенту, которого больше нет.

Как лучше с этим справиться? Я предпочитаю просто закрыть серверное соединение с клиентом и двигаться дальше, а не выходить из всей программы.

PS: вопрос / ответ Этот рассматривает проблему в общем виде; как конкретно я должен это решить?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
51
0
85 539
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

Прочтите заявление try :.

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

Если я попробую: #something except: #anything, будет ли он что-нибудь улавливать, а не только ошибки ввода-вывода?

Adam Plumb 08.10.2008 00:28

Одеяло - это плохая политика. Но все же он перехватит любое исключение. Вы знаете, что это ошибка IOError. Разберись с этим. Если возникает что-то еще, выясните, почему, и обработайте это соответствующим образом. Вы не хотите маскировать такие ошибки, как деление на ноль или нехватка памяти.

S.Lott 08.10.2008 00:46

Если вы используете модуль сокета Python, вы не получите исключение IOError: вы получите исключение socket.error.

mhawke 08.10.2008 04:17

Вы не получите IOError. socket.error не имеет атрибута errno для сломанного канала - этот код вызовет AttributeError.

mhawke 08.10.2008 04:55

Вы не получите IOError с errno == EPIPE для исключений сломанных трубных сокетов, вы получите socket.error, поэтому нет смысла проверять его в обработчике исключений IOError. У вас 2 голоса за (все еще) плохой ответ. Возможно, вам стоит проголосовать за мой ответ :)

mhawke 08.10.2008 05:27

На этом этапе спрашивающий должен знать, как использовать оператор try :.

S.Lott 08.10.2008 05:39

Согласитесь, что теперь у спрашивающего должно быть какое-то представление о том, что делать. Тем не менее, ваш фрагмент кода по-прежнему неверен, и спрашивающий может последовать вашему примеру. Это не сломает его код, просто проверка EPIPE в обработчике IOError бесполезна.

mhawke 08.10.2008 05:45

@mhawke: ты все еще прав. Оба раза. Однако сложно придумать пример стандартных ошибок ОС (с errno) и других ошибок (без errno). Я думаю, что важно иметь аккуратный образец кода - я не пишу их приложение для них.

S.Lott 08.10.2008 05:54

Общие исключения обычно могут быть плохой политикой, но в данном случае это не так. Это более или менее то, для чего нужны исключения. Почти всегда лучше использовать операторы if для проверки того, что введенные вами входные данные и вызовы не будут вызывать ошибок, потому что это не испортит вашу отладку. Однако сокеты имеют ресурсы более низкого уровня, поэтому к ним можно легко получить доступ с помощью модуля сокета python. Поскольку мы не можем получить доступ к каналу и правильно обработать его на нижнем уровне, можно использовать исключения.

trevorKirkby 07.11.2014 20:37

SIGPIPE (хотя, может быть, вы имеете в виду EPIPE?) Возникает на сокетах, когда вы закрываете сокет, а затем отправляете на него данные. Простое решение - не отключать сокет перед попыткой отправить ему данные. Это также может произойти с каналами, но это не похоже на то, что вы испытываете, поскольку это сетевой сервер.

Вы также можете просто применить бандаж для перехвата исключения в каком-либо обработчике верхнего уровня в каждом потоке.

Конечно, если бы вы использовали Скрученный вместо создания нового потока для каждого клиентского соединения, у вас, вероятно, не было бы этой проблемы. Действительно сложно (возможно, невозможно, в зависимости от вашего приложения) правильно упорядочить операции закрытия и записи, если несколько потоков работают с одним и тем же каналом ввода-вывода.

Простое решение - не отключать сокет перед попыткой отправить ему данные. You assume here that the socket was shut down locally (on the server side) whereas in это answer we read that Обычно это происходит, когда вы пишете в полностью закрытый сокет на другой (клиентской) стороне. Did you omit this case by purpose or you don't agree with this statement?
Piotr Dobrogost 02.12.2014 18:35

Мой ответ очень близок к ответу С.Лотта, за исключением того, что я был бы еще более конкретным:

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.

mhawke 08.10.2008 04:24

Я должен был пояснить, что имел в виду «23» в качестве заполнителя. Действительно? 32? Я был ближе, чем мог предположить. :-)

Kirk Strauser 08.10.2008 06:55

Предполагая, что вы используете стандартный модуль сокета, вы должны уловить исключение 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):. Кто-нибудь может это объяснить?

guettli 12.05.2016 17:37

Это означает, что "e.args - это кортеж?"

mjz19910 25.05.2016 09:08

Почему EPIPE не всегда возникает при первой записи? При каком условии возникает EPIPE?

tartaruga_casco_mole 22.01.2021 19:05

Я сталкиваюсь с тем же вопросом. Но в следующий раз я отправлю тот же код, он просто работает. Первый раз сломалось:

$ packet_write_wait: Connection to 10.. port 22: Broken pipe

Второй раз работает:

[1]   Done                    nohup python -u add_asc_dec.py > add2.log 2>&1

Я предполагаю, что причина может быть в текущей серверной среде.

Другие вопросы по теме