如果在超时发生之前没有收到数据,Python 的 socket.recv() 会为非阻塞套接字返回啥?

Posted

技术标签:

【中文标题】如果在超时发生之前没有收到数据,Python 的 socket.recv() 会为非阻塞套接字返回啥?【英文标题】:What does Python's socket.recv() return for non-blocking sockets if no data is received until a timeout occurs?如果在超时发生之前没有收到数据,Python 的 socket.recv() 会为非阻塞套接字返回什么? 【发布时间】:2013-05-20 16:06:34 【问题描述】:

基本上,我在几个地方读到socket.recv() 将返回它可以读取的任何内容,或者一个表明对方已关闭的空字符串(官方文档甚至没有提到连接时返回的内容已关闭...太好了!)。对于阻塞套接字来说,这一切都很好而且很花哨,因为我们知道recv() 只在实际有东西要接收时才返回,所以当它返回一个空字符串时,它必须表示另一端已经关闭连接,对吧?

好的,好的,但是当我的套接字非阻塞时会发生什么?我已经搜索了一下(可能还不够,谁知道?)并且无法弄清楚如何判断对方何时使用非阻塞套接字关闭了连接。似乎没有任何方法或属性可以告诉我们这一点,将recv() 的返回值与空字符串进行比较似乎完全没用……难道只有我有这个问题吗?

作为一个简单的例子,假设我的套接字超时设置为 1.2342342(您喜欢这里的任何非负数)秒,我调用socket.recv(1024),但在这 1.2342342 秒期间,另一端没有发送任何内容。 recv() 调用将返回一个空字符串,我不知道连接是否仍然存在......

【问题讨论】:

"(官方文档甚至没有提到连接关闭时返回的内容......太好了!)"。七年过去了,还是没有修好。这太令人沮丧了,花了将近一个小时试图弄清楚为什么我的套接字在连接关闭时没有引发错误。然后我就像好的让我们设置一个超时。这也没有引发异常,我真的很困惑。感谢您的帖子,否则我仍然会尝试解决这个问题。 这个问题在 Python 3 中仍然存在吗?我认为 OP 很可能在 Python 2 中遇到过这个问题,因为这个问题是在 2013 年提出的。 2021 年版,python 3 文档仍然没有真正提供对损坏或断开连接的返回值的清晰描述。尽管示例代码确实将 b'' 作为逻辑比较。感谢您的提问! (和答案):) docs.python.org/3/library/socket.html 【参考方案1】:

当您将recvselect 连接使用时,如果套接字已准备好从中读取但没有要读取的数据,则意味着客户端已关闭连接。

这里有一些处理这个问题的代码,还请注意在 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 是否会返回客户端套接字以指示已读取,如果套接字已关闭?我想这也是问题,您的回答表明,poll 将选择要读取的套接字,我可以执行 socket.recv 并检查是否有任何数据来检查连接是否关闭【参考方案2】:

在没有可用数据的非阻塞套接字的情况下,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,但在超时的情况下,异常的伴随值始终是设置为“超时”的字符串。因此,要处理这种情况,您可以这样做:

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

正如 cmets 中所指出的,这也是一种更便携的解决方案,因为它不依赖于操作系统特定的功能来将套接字置于非阻塞模式。

请参阅recv(2) 和python socket 了解更多详情。

【讨论】:

好的,这正是我需要知道的。谢谢!如果只有官方文档提到这种方法引发了异常,我就不需要问这个问题了……对文档有点失望:(但对你的回答非常满意:)竖起大拇指!跨度> 只是关于您的代码的旁注。以更 Pythonic 和跨平台(恕我直言,没有 C 风格标志)的方式将套接字设置为非阻塞模式,只需调用 s.settimeout(whatever_nonnegative_number_of_your_liking) 实际上,我现在才注意到这只能回答我的一半问题。问题是,当s 定义了超时并且recv() 在超时后没有数据而失败时,会引发socket.timeout 异常(不是socket.error)。超时异常仍然不能让我得出任何关于连接状态的结论。我假设如果连接关闭,则会引发socket.error。谁能证实这一点? 更新了示例以解决您对使用 s.settimeout() 的非阻塞行为的评论。你是对的,那里的情况不同。 有python socket.setblocking() 方法可以让socket变成非阻塞的【参考方案3】:

很简单:如果recv()返回0字节; you will not receive any more data on this connection. Ever.你仍然可以发送。

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

【讨论】:

感谢您的回答。很好很简单。我阅读了文档,但我的大脑似乎以某种方式忽略了那部分:D 最后,在实际考虑了一下之后,我注意到 recv() 永远不会返回空字符串,除非连接已断开,因为在非阻塞当在超时期间没有可用数据时,模式recv() 将引发socket.timeout。再次感谢! :)【参考方案4】:

为了完成现有的答案,我建议using select instead of nonblocking sockets。关键是非阻塞套接字使事情复杂化(可能除了发送),所以我想说根本没有理由使用它们。如果您经常遇到应用程序在等待 IO 时被阻塞的问题,我也会考虑在后台的单独线程中执行 IO。

【讨论】:

当你需要它做其他事情时让你的线程阻塞(可能无限期地)也会使事情复杂化......并且即使在选择之后套接字操作也有可能(至少在 Linux 下)阻塞() 表示它不会:(

以上是关于如果在超时发生之前没有收到数据,Python 的 socket.recv() 会为非阻塞套接字返回啥?的主要内容,如果未能解决你的问题,请参考以下文章

详解 TCP 超时与重传机制——长文预警

注销时清除超时的问题

如果连接超时硒python,请刷新网页

在Python3中,socket.recv方法如果一段时间内没有收到返回,如何让这段代码跳过,并执行下一步操作

SQL 超时故障排除

SqlException:超时已过期