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

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

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

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

4 ответов


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

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 :)

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

посмотреть recv (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'

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

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


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