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


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

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

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

4 44

4 ответа:

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

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 для получения более подробной информации.

Когда вы используете 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'

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

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

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