Как определить, что соединение в Python не работает? - PullRequest
55 голосов
/ 20 марта 2009

Я хочу, чтобы мое приложение на Python могло определять, когда сокет на другой стороне был удален. Есть ли способ для этого?

Ответы [ 5 ]

44 голосов
/ 02 марта 2013

Краткий ответ:

используйте неблокирующую функцию recv () или блокирующую функцию recv () / select () с очень короткий тайм-аут.

Длинный ответ:

Способ обработки соединений с сокетами - читать или писать по мере необходимости и быть готовым к обработке ошибок соединения.

TCP различает 3 формы «сбрасывания» соединения: таймаут, сброс, закрытие.

Из них тайм-аут на самом деле не может быть обнаружен, TCP может только сказать вам, что время еще не истекло. Но даже если бы он сказал вам об этом, время может истечь сразу после.

Также помните, что при использовании shutdown () вы или ваш коллега (другой конец соединения) можете закрыть только входящий поток байтов и сохранить работающий исходящий поток байтов, или закрыть исходящий поток и сохранить работающий входящий. .

Строго говоря, вы хотите проверить, закрыт ли поток чтения, закрыт ли поток записи или закрыты оба.

Даже если соединение было «разорвано», вы все равно сможете прочитать любые данные, которые все еще находятся в сетевом буфере. Только после того, как буфер опустеет, вы получите отключение от recv ().

Проверка разрыва соединения - это все равно, что спросить "что я получу после прочтения всех данных, которые в данный момент буферизируются?" Чтобы это выяснить, вам просто нужно прочитать все данные, которые в данный момент находятся в буфере.

Я вижу, как «чтение всех буферизованных данных», чтобы добраться до конца, может быть проблемой для некоторых людей, которые до сих пор считают recv () блокирующей функцией. При блокировке recv () блокируется «проверка» на чтение, когда буфер уже пуст, что лишает цели «проверки».

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

Что вы можете сделать:

  • установить сокет в неблокирующий режим, но затем вы получите системно-зависимую ошибку, указывающую, что буфер приема пуст или буфер отправки заполнен
  • придерживаться режима блокировки, но установить очень короткий тайм-аут сокета. Это позволит вам «пинговать» или «проверять» сокет с помощью recv (), в значительной степени то, что вы хотите сделать
  • используйте вызов select () или асинхронный модуль с очень коротким тайм-аутом. Отчет об ошибках по-прежнему зависит от системы.

Что касается проблемы записи, то поддержание пустых буферов чтения в значительной степени покрывает это. Вы обнаружите, что соединение «сброшено» после неблокирующей попытки чтения, и вы можете прекратить отправку чего-либо после того, как чтение вернет закрытый канал.

Полагаю, единственный способ убедиться, что ваши отправленные данные достигли другого конца (и еще не находится в буфере отправки), это:

  • получите правильный ответ в том же сокете для точного отправленного вами сообщения. В основном вы используете протокол более высокого уровня для подтверждения.
  • успешно выполнить shutdow () и close () на сокете

Python-сокет howto сообщает, что send () вернет 0 байт, записанных, если канал закрыт. Вы можете использовать неблокирующую или timeout socket.send (), и если она возвращает 0, вы больше не можете отправлять данные в этот сокет. Но если он возвращает ненулевое значение, вы уже отправили что-то, удачи вам:)

Также здесь я не рассматривал данные сокетов OOB (out-of-band) здесь как средство для решения вашей проблемы, но я думаю, что OOB был не тем, что вы имели в виду.

34 голосов
/ 20 марта 2009

Зависит от того, что вы подразумеваете под «уронили». Для TCP-сокетов, если другой конец закрывает соединение либо через close () или процесс завершается, вы узнаете, прочитав конец файла или получив ошибку чтения, обычно в качестве значения errno устанавливается значение «сброс соединения по одноранговой сети» вашей операционной системой. Для python вы будете читать строку нулевой длины, или будет выдан socket.error при попытке чтения или записи из сокета.

14 голосов
/ 20 марта 2009

Из ссылки Jweede размещено:

исключение socket.timeout:

This exception is raised when a timeout occurs on a socket
which has had timeouts enabled via a prior call to settimeout().
The accompanying value is a string whose value is currently
always “timed out”.

Вот демонстрационные серверные и клиентские программы для модуля сокетов из документов python

# Echo server program
import socket

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
    data = conn.recv(1024)
    if not data: break
    conn.send(data)
conn.close()

И клиент:

# Echo client program
import socket

HOST = 'daring.cwi.nl'    # The remote host
PORT = 50007              # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)

На странице примеров документов, из которой я их извлек, есть более сложные примеры, в которых используется эта идея, но вот простой ответ:

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

try:
    s.connect((HOST, PORT))
    s.send("Hello, World!")
    ...
except socket.timeout:
    # whatever you need to do when the connection is dropped
4 голосов
/ 27 июня 2017

Я перевел пример кода в этом сообщении в блоге на Python: Как определить, когда клиент закрывает соединение? , и он хорошо работает для меня:

from ctypes import (
    CDLL, c_int, POINTER, Structure, c_void_p, c_size_t,
    c_short, c_ssize_t, c_char, ARRAY
)


__all__ = 'is_remote_alive',


class pollfd(Structure):
    _fields_ = (
        ('fd', c_int),
        ('events', c_short),
        ('revents', c_short),
    )


MSG_DONTWAIT = 0x40
MSG_PEEK = 0x02

EPOLLIN = 0x001
EPOLLPRI = 0x002
EPOLLRDNORM = 0x040

libc = CDLL(None)

recv = libc.recv
recv.restype = c_ssize_t
recv.argtypes = c_int, c_void_p, c_size_t, c_int

poll = libc.poll
poll.restype = c_int
poll.argtypes = POINTER(pollfd), c_int, c_int


class IsRemoteAlive:  # not needed, only for debugging
    def __init__(self, alive, msg):
        self.alive = alive
        self.msg = msg

    def __str__(self):
        return self.msg

    def __repr__(self):
        return 'IsRemoteClosed(%r,%r)' % (self.alive, self.msg)

    def __bool__(self):
        return self.alive


def is_remote_alive(fd):
    fileno = getattr(fd, 'fileno', None)
    if fileno is not None:
        if hasattr(fileno, '__call__'):
            fd = fileno()
        else:
            fd = fileno

    p = pollfd(fd=fd, events=EPOLLIN|EPOLLPRI|EPOLLRDNORM, revents=0)
    result = poll(p, 1, 0)
    if not result:
        return IsRemoteAlive(True, 'empty')

    buf = ARRAY(c_char, 1)()
    result = recv(fd, buf, len(buf), MSG_DONTWAIT|MSG_PEEK)
    if result > 0:
        return IsRemoteAlive(True, 'readable')
    elif result == 0:
        return IsRemoteAlive(False, 'closed')
    else:
        return IsRemoteAlive(False, 'errored')
4 голосов
/ 20 марта 2009

Если я не ошибаюсь, это обычно выполняется через timeout .

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