Когда и почему socket.send() возвращает 0 в python?
python3 сокет программирует howto представляет этот фрагмент кода
class MySocket:
"""demonstration class only
- coded for clarity, not efficiency
"""
def __init__(self, sock=None):
if sock is None:
self.sock = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
else:
self.sock = sock
def connect(self, host, port):
self.sock.connect((host, port))
def mysend(self, msg):
totalsent = 0
while totalsent < MSGLEN:
sent = self.sock.send(msg[totalsent:])
if sent == 0:
raise RuntimeError("socket connection broken")
totalsent = totalsent + sent
def myreceive(self):
chunks = []
bytes_recd = 0
while bytes_recd < MSGLEN:
chunk = self.sock.recv(min(MSGLEN - bytes_recd, 2048))
if chunk == b'':
raise RuntimeError("socket connection broken")
chunks.append(chunk)
bytes_recd = bytes_recd + len(chunk)
return b''.join(chunks)
где цикл отправки прерывается, если метод socket send
возвращает 0.
Логика этого фрагмента заключается в том, что когда метод send
возвращает "0 отправленных байт", отправляющая сторона соединения сокета должна отказаться от своих усилий по отправке данных. Это верно для метода recv
, где нулевые байты, считанные для сокета в режиме блокировки, должны интерпретироваться как EOF
и поэтому сторона чтения должна отказаться.
Однако я не могу понять, в каких ситуациях метод send
мог возвращать ноль. Мое понимание сокетов python заключается в том, что send
немедленно возвращается из-за буферизации на уровне ОС. Если буфер заполнен, send
будет заблокирован, или если соединения будут закрыты на удаленной стороне, будет создано исключение.
Наконец, предположим, что send
возвращает ноль без повышения исключения: действительно ли это означает, что все будущие вызовы send
будут возвращать ноль?
Я провел некоторое тестирование (хотя использовал только сокет, подключенный к ::1
в OS X) и не смог найти ситуацию, в которой send
возвращает 0.
Edit
В HOWTO указано:
Но если вы планируете повторно использовать ваш сокет для дальнейших передач, вам нужно чтобы понять, что на сокете нет EOT. Повторяю: если сокет send или recv возвращает после обработки 0 байтов, соединение было сломана. Если соединение не было сломано, вы можете подождать на recv навсегда, потому что сокет не скажет вам, что ничего нет больше читать (пока).
Очень легко найти ситуацию, в которой recv
возвращает 0: когда удаленная (посылающая) сторона вызывает socket.shutdown(SHUT_WR)
, далее recv
на принимающей стороне вернет 0
и не приведет к возникновению каких-либо исключений.
Я ищу конкретный пример, где вы можете показать, что прием 0 нуля из send
указывает на сломанное соединение (которое будет продолжать возвращать 0 при отправке.)
Ответы
Ответ 1
Увидев вопрос, я был как-то ошеломлен, потому что вызов send
C может возвращать 0 байтов, и соединение, конечно, все еще живое (сокет не может просто отправить больше байтов в данный данный момент времени)
Я решил "использовать источник", и если я не ошибаюсь (что всегда бывает и часто бывает), это ошибка в HOWTO.
Сеть:
-
send
является псевдонимом для sock_send
-
sock_send
вызывает по очереди sock_call
-
sock_call
вызывает по очереди sock_call_ex
-
sock_call
вызывает по очереди sock_send_impl
(который был передан по цепочке, начинающейся с sock_send
)
разматывать:
Цепочка показывает, что если отправлено 0
байтов, ошибки нет, и это действительно является потенциальной ситуацией в реальной жизни.
Если моя логика неправильная, кто-то, пожалуйста, дайте мне знать.
Ответ 2
Возможно, я ошибаюсь, но я думаю, что вы ищете невозможную ситуацию...
Как пояснил @mementum в своем ответе, теоретически возможно, что сокет будет возвращать ноль, если нет ошибки, но также не отправлено данных.
Однако, как показано в другом месте на SO, это может произойти только в очень специфических сценариях. По моему опыту (а также в комментариях к принятому ответу), вы только когда-либо получите нулевой результат в неблокирующем сокете, когда сеть перегружена. Теперь сокеты Python блокируются по умолчанию, а это значит, что ядро должно подождать, пока не будет места, чтобы взять еще несколько данных, а затем вернуть количество байтов в очереди. По определению это никогда не может быть нулевым.
Итак, все вместе, так как ваш фрагмент не reset тип сокета - например. используя функцию set_blocking
- она использует блокирующие сокеты и поэтому не может возвращать нуль и, следовательно, не может ударить по указанному пути.
Это подтверждается тем фактом, что вы не смогли запустить определенную строку кода независимо от того, что вы делаете.