Как обработать сломанную трубу (SIGPIPE) в Python? - PullRequest
45 голосов
/ 07 октября 2008

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

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

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

Ответы [ 5 ]

51 голосов
/ 08 октября 2008

Предполагая, что вы используете стандартный модуль сокета, вы должны перехватить исключение 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 отлично подходит для такого рода вещей, однако, похоже, вы уже написали немало кода.

37 голосов
/ 08 октября 2008

Прочитайте инструкцию try:

try:
    # do something
except socket.error, e:
    # A socket error
except IOError, e:
    if e.errno == errno.EPIPE:
        # EPIPE error
    else:
        # Other error
3 голосов
/ 08 октября 2008

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

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

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

0 голосов
/ 26 октября 2017

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

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

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

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

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

0 голосов
/ 08 октября 2008

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

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. Таким образом, вы не будете пытаться обработать ошибку прав доступа или что-то еще, к чему вы не готовы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...