如果在非阻塞套接字上调用Python的socket.recv()方法时,在超时之前没有接收到任何数据,它会返回什么?

90

基本上,我在几个地方读到过 socket.recv() 会返回其可以读取的任何内容,或者一个空字符串,表示对方已关闭连接(官方文档甚至没有提到在连接关闭时它会返回什么...太好了!)。这对于阻塞式套接字来说是完全可以的,因为我们知道只有在实际有数据可接收时recv()才会返回,所以当它返回一个空字符串时,它必须意味着另一端关闭了连接,对吧?

好的,但是如果我的套接字是非阻塞的呢?我搜索过一些资料(也许不够,谁知道呢?)却无法弄清楚如何使用非阻塞套接字告诉其他端口何时关闭连接。似乎没有方法或属性可以告诉我们这一点,而将recv()的返回值与空字符串进行比较似乎毫无用处...难道只有我遇到这个问题吗?

作为一个简单的示例,假设我的套接字超时设置为1.2342342(在此处输入任何非负数),然后我调用socket.recv(1024) ,但在那1.2342342秒间期间,对方没有发送任何数据。 recv()调用将返回一个空字符串,我不知道连接是否仍然存在...


9
“(官方文档甚至没有提到连接关闭时返回的内容……太好了!)七年过去了,这个问题还未解决。这真让人沮丧,我花了将近一个小时来尝试弄清楚为什么我的套接字在连接关闭时没有引发错误。然后我想好了,那就设置一个超时时间吧。但这也没有引发异常,我感到非常困惑。谢谢你的帖子,否则我还在努力理解中。” - KoKlA
1
这个问题在 Python 3 中仍然存在吗?我认为 OP 可能在 Python 2 中遇到了这个问题,因为该问题是在2013年提出的。 - icedwater
截至2021年底,Python 3文档仍未真正提供有关损坏或丢失连接的返回值的清晰描述。尽管示例代码确实查找b''作为逻辑比较。感谢提问!(和答案) :) https://docs.python.org/3/library/socket.html - Chemistpp
4个回答

104

对于没有可用数据的非阻塞套接字,recv会抛出socket.error异常,并且异常的值将具有EAGAIN或EWOULDBLOCK的errno。例如:

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异常,但是在超时的情况下,异常的附带值始终设置为“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 :)

评论中提到,这也是一种更具可移植性的解决方案,因为它不依赖于操作系统特定的功能来将套接字置于非阻塞模式。

有关详细信息,请参见recv(2)Python套接字


2
好的,这正是我需要知道的。谢谢!如果官方文档提到该方法会引发异常,我就不必问这个问题了...有点失望官方文档:( 但非常高兴得到了你的答案:) 点赞! - El Ninja Trepador
2
关于你的代码,我有一个小建议。在更Pythonic和跨平台(并且在我看来更易读,不需要C风格的标志)的方式中将套接字设置为非阻塞模式,只需简单地调用s.settimeout(whatever_nonnegative_number_of_your_liking)即可。 - El Ninja Trepador
实际上,我现在才注意到这只回答了我的问题的一半。问题是,当s定义了超时并且recv()在超时后没有数据失败时,会引发socket.timeout异常(而不是socket.error)。超时异常仍然无法让我得出关于连接状态的任何结论。虽然我认为如果连接关闭,则会引发socket.error。有人可以确认吗? - El Ninja Trepador
1
更新了示例以解决您对s.settimeout()的非阻塞行为的评论。您所说的情况确实有所不同。 - mshildt
1
有Python中的socket.setblocking()方法可以使套接字变为非阻塞状态。 - Zaar Hai
显示剩余4条评论

10

简单来说:如果 recv() 返回 0 字节;这个连接将不会再接收到任何数据。永远。 但你可能仍然可以发送数据。

这意味着,如果非阻塞套接字没有可用数据但连接仍然存活(另一端可能会发送数据),它必须引发异常(这可能是系统相关的)。


谢谢你的回答。简单明了。我读了文档,但我的大脑似乎忽略了那部分哈哈。 最后,在认真思考了一会儿之后,我注意到recv()只有在连接断开时才会返回空字符串,因为在非阻塞模式下,当超时期间没有可用数据时,recv()会引发socket.timeout异常。再次感谢!:) - El Ninja Trepador

8
当您在使用selectrecv配合使用时,如果套接字已准备好读取但没有数据可读取,则表示客户端已关闭连接。
以下是一些处理这种情况的代码,还请注意在while循环中第二次调用recv时抛出的异常。如果没有剩余可读数据,则会抛出此异常,但这并不意味着客户端已关闭连接:
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'

这是我所面临的情况:我们使用select.poll,我想知道如果套接字已关闭,是否会返回客户端套接字以指示读取?我猜这也是问题,你的答案表明,poll将选择套接字进行读取,我可以执行socket.recv并检查是否有任何数据来检查连接是否关闭。 - Alex Punnen

2

为了完善已有的答案,我建议使用select而非非阻塞式套接字。原因是非阻塞式套接字会使事情变得复杂(除了发送之外),所以我认为根本没有必要使用它们。如果您经常遇到应用程序因等待IO而被阻塞的问题,我还建议在后台使用单独的线程进行IO。


7
当您需要线程执行其他任务时,它可能会被阻塞(甚至是无限期地),这也可能会使事情变得更加复杂......并且有可能(至少在Linux下)在select()指示不会阻塞的情况下,套接字操作仍会被阻塞。 - Jeremy Friesner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接