Что делает python's socket.recv () возвращает для неблокирующих сокетов, если данные не получены до тех пор, пока не наступит тайм-аут?
В основном, я прочитал в нескольких местах, что socket.recv()
вернет все, что он может прочитать, или пустую строку, сигнализирующую о том, что другая сторона закрылась (официальные документы даже не упоминают, что она возвращает, когда соединение закрыто... отлично!). Это все прекрасно и прекрасно для блокировки сокетов, так как мы знаем, что recv()
возвращается только тогда, когда действительно есть что-то, чтобы получить, поэтому, когда он возвращает пустую строку, это должно означать, что другая сторона закрыла соединение, так ведь?
recv()
с пустой строкой кажется абсолютно бесполезным... это только у меня такая проблема?
В качестве простого примера предположим, что таймаут моего сокета установлен в 1.2342342 (любое неотрицательное число, которое вам здесь нравится) секунды, и я звоню socket.recv(1024)
, но другая сторона ничего не посылает в течение этого 1.2342342 второго периода. Вызов recv()
вернет пустую строку, и я понятия не имею, сохраняется ли соединение или нет...
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 вместо неблокирующих сокетов. Дело в том, что неблокирующие сокеты усложняют работу (за исключением, возможно, отправки), поэтому я бы сказал, что нет никакой причины использовать их вообще. Если у вас регулярно возникает проблема, что ваше приложение блокируется в ожидании ввода-вывода, я бы также рассмотрел возможность выполнения ввода-вывода в отдельном потоке в фоновом режиме.