Как обрабатывать сломанную трубу (SIGPIPE) в python?

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

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

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

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

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

Ответ 5

Мой ответ очень близок к S.Lott's, за исключением того, что я был бы более конкретным:

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