Что возвращает Python socket.recv() для неблокирующих сокетов, если данные не получены до тех пор, пока не произойдет тайм-аут?

В принципе, я читал в нескольких местах, что socket.recv() вернет все, что он может прочитать, или пустую строку, сигнализирующую о том, что другая сторона закрыта (официальные документы даже не упоминают, что она возвращает, когда соединение закрыт... здорово!). Это все прекрасно и денди для блокировки сокетов, так как мы знаем, что recv() возвращается только тогда, когда на самом деле есть что-то получить, поэтому, когда он возвращает пустую строку, она MUST означает, что другая сторона закрыта соединение, правильно?

Хорошо, хорошо, но что происходит, когда мой сокет не блокируется? Я немного искал (может быть, недостаточно, кто знает?) И не может понять, как сказать, когда другая сторона закрыла соединение, используя неблокирующий сокет. Кажется, нет никакого метода или атрибута, который говорит нам об этом, и сравнение возвращаемого значения recv() с пустой строкой кажется абсолютно бесполезным... это только у меня есть эта проблема?

В качестве простого примера предположим, что мой тайм-аут сокета установлен на 1.2342342 (любой неотрицательный номер, который вам нравится здесь) секунд, и я вызываю socket.recv(1024), но другая сторона ничего не отправляет в течение этого второго периода 1.2342342. Вызов recv() возвращает пустую строку, и я не знаю, остается ли соединение еще стоять или нет...

Ответ 1

В случае неблокирующего сокета, у которого нет доступных данных, recv будет генерировать исключение socket.error, а значение исключения будет иметь errno либо EAGAIN, либо EWOULDBLOCK. Пример:

import sys
import socket
import fcntl, os
import errno
from time import sleep

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
fcntl.fcntl(s, fcntl.F_SETFL, os.O_NONBLOCK)

while True:
    try:
        msg = s.recv(4096)
    except socket.error, e:
        err = e.args[0]
        if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
            sleep(1)
            print 'No data available'
            continue
        else:
            # a "real" error occurred
            print e
            sys.exit(1)
    else:
        # got a message, do something :)

Ситуация немного отличается в случае, когда вы включили неблокирующее поведение через тайм-аут с socket.settimeout(n) или socket.setblocking(False). В этом случае socket.error повышается, но в случае тайм-аута, сопровождающее значение исключения всегда представляет собой строку, установленную на "тайм-аут". Итак, для обработки этого случая вы можете:

import sys
import socket
from time import sleep

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
s.settimeout(2)

while True:
    try:
        msg = s.recv(4096)
    except socket.timeout, e:
        err = e.args[0]
        # this next if/else is a bit redundant, but illustrates how the
        # timeout exception is setup
        if err == 'timed out':
            sleep(1)
            print 'recv timed out, retry later'
            continue
        else:
            print e
            sys.exit(1)
    except socket.error, e:
        # Something else happened, handle error, exit, etc.
        print e
        sys.exit(1)
    else:
        if len(msg) == 0:
            print 'orderly shutdown on server end'
            sys.exit(0)
        else:
            # got a message do something :)

Как указано в комментариях, это также более портативное решение, поскольку оно не зависит от конкретных функций ОС, чтобы поместить сокет в неблокирующий режим.

Смотрите recv (2) и python socket для более подробная информация.

Ответ 2

Когда вы используете recv в связи с select, если сокет готов к чтению, но нет данных для чтения, что означает, что клиент закрыл соединение.

Вот некоторый код, который обрабатывает это, также обратите внимание на исключение, которое вызывается, когда recv вызывается второй раз в цикле while. Если не осталось ничего, чтобы прочитать это исключение, это не означает, что клиент закрыл соединение:

def listenToSockets(self):

    while True:

        changed_sockets = self.currentSockets

        ready_to_read, ready_to_write, in_error = select.select(changed_sockets, [], [], 0.1)

        for s in ready_to_read:

            if s == self.serverSocket:
                self.acceptNewConnection(s)
            else:
                self.readDataFromSocket(s)

И функция, которая получает данные:

def readDataFromSocket(self, socket):

    data = ''
    buffer = ''
    try:

        while True:
            data = socket.recv(4096)

            if not data: 
                break

            buffer += data

    except error, (errorCode,message): 
        # error 10035 is no data available, it is non-fatal
        if errorCode != 10035:
            print 'socket.error - ('+str(errorCode)+') ' + message


    if data:
        print 'received '+ buffer
    else:
        print 'disconnected'

Ответ 3

Это просто: если recv() возвращает 0 байт; вы не получите больше данных об этом соединении. Всегда. Вы все еще можете отправить.

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

Ответ 4

Чтобы завершить существующие ответы, я предлагаю использовать select вместо неблокирующих сокетов. Дело в том, что неблокирующие сокеты усложняют материал (за исключением, возможно, отправки), поэтому я бы сказал, что нет причин использовать их вообще. Если вы регулярно сталкиваетесь с проблемой, что ваше приложение заблокировано, ожидая ввода-вывода, я также рассмотрю возможность ввода IO в отдельном потоке в фоновом режиме.