python3「非阻塞socket」报错 “BlockingIOError: [Errno 11]“ 复现以及分析解决

2023-11-05

梦想还在,生活当继续!

一、前言

linux 下,用 python 的非阻塞 socket 通信时,遇到了 BlockingIOError: [Errno 11] Resource temporarily unavailable 错误。

翻译报错信息 Resource temporarily unavailable 为:“资源暂时不可用”。

在我的代码里,使用了“epoll + 非阻塞 socket” 的模式。因此猜测,在有 socket 还未创建完成的情况下,使用它发送消息导致报错,错误的理解为这个 socket 资源暂时不可用。-.-

后来上网查找相关资料,发现并非如此。根据网上资料,我得到两种不同的答案。

答案一:

首先,是我把套接字设置为异步的。然后,在使用 write 发送数据时采取的方式是循环发送大量的数据;由于是异步的,write\send 将要发送的数据提交到发送缓冲区后是立即返回的,并不需要对端确认数据已接收。在这种情况下是很有可能出现发送缓冲区被填满,导致 write\send 无法再向缓冲区提交要发送的数据。因此就产生了 Resource temporarily unavailable 的错误。EAGAIN 的意思也很明显,就是要你再次尝试。

答案二:

Linux 进行非阻塞的 socket 接收数据时经常出现 Resource temporarily unavailableerrno 代码为11(EAGAIN),这是什么意思?
这表明你在非阻塞模式下调用了阻塞操作,在该操作没有完成就返回这个错误,这个错误不会破坏 socket 的同步,不用管它,下次循环接着 recv 就可以。

两种答案不同,但是感觉都很有道理。这让我迷惑了,于是决定自己研究研究。记录下来,分享的同时,也方便自己以后回顾。

二、BlockingIOError

首先,我想知道 python 中的 BlockingIOError 具体指什么。
python 官方文档中,找到了对 BlockingIOError 异常的说明,如下:

exception BlockingIOError:

Raised when an operation would block on an object (e.g. socket) set for non-blocking operation. Corresponds to errno EAGAIN, EALREADY, EWOULDBLOCK and EINPROGRESS.

我将它翻译为:“当在设置为非阻塞操作的对象(例如:套接字)上,执行阻塞操作时触发。对应的错误类型有:EAGAIN, EALREADY, EWOULDBLOCKEINPROGRESS。”

linux 下,BlockingIOError: [Errno 11] 即为 EAGAIN 错误。

windows 上,EAGAIN 的名字叫做 EWOULDBLOCK。对应的报错信息为:
BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作”。

官网的这个说明,依然没让我明白为什么会出现“资源不可用”。但它大概描述了如何触发 EAGAIN,于是我决定复现它,继续研究。

三、EAGAIN 复现

据我了解,可以触发 EAGAIN 错误的操作有:accept()recv()send()

connect() 方法也会阻塞,但返回的是 EINPROGRESS 错误,表示连接操作正在进行中,但是仍未完成。

因此,我在 linux 下使用 python3,分别复现三种函数触发 EAGAIN 异常。

3.1、accept() 触发

accept() 触发很简单,一个程序就能完成,代码如下:

# _*_ coding:utf-8 _*_
"""
tcp服务端
"""
import socket


def tcp_server():
    server = socket.socket()
    # 设置为非阻塞
    server.setblocking(False)
    address = ('0.0.0.0', 80)
    server.bind(address)
    server.listen(3)
    cli, addr = server.accept()


if __name__ == '__main__':
    tcp_server()

运行代码,报错如下:

Traceback (most recent call last):
  File "tcp_server.py", line 19, in <module>
    tcp_server()
  File "tcp_server.py", line 15, in tcp_server
    cli, addr = server.accept()
  File "/usr/lib64/python3.6/socket.py", line 205, in accept
    fd, addr = self._accept()
BlockingIOError: [Errno 11] Resource temporarily unavailable

可以看到,在 server.accept() 时,触发了 [Errno 11]

3.2、recv() 触发

recv() 触发,需要两个程序,一个服务端,一个客户端。在服务端 recv() 客户端发送的消息时,触发异常。

服务端代码,如下:

# _*_ coding:utf-8 _*_
"""
tcp服务端
"""
import socket


def tcp_server():
    server = socket.socket()
    address = ('0.0.0.0', 80)
    server.bind(address)
    server.listen(3)
    cli, addr = server.accept()

    # 将返回的客户端连接socket设置为非阻塞
    cli.setblocking(False)
    cli.recv(1024)


if __name__ == '__main__':
    tcp_server()

客户端代码,如下:

# _*_ coding:utf-8 _*_
"""
tcp客户端
"""
import socket


def tcp_client():
    ip_port = ('127.0.0.1', 80)
    client = socket.socket()
    client.connect(ip_port)
    client.send('hello world!'.encode())
    print(f'send data success.')


if __name__ == '__main__':
    tcp_client()

首先,执行服务端代码;然后,执行客户端代码访问服务端,服务端将触发错误。

服务端,报错如下:

Traceback (most recent call last):
  File "tcp_server.py", line 21, in <module>
    tcp_server()
  File "tcp_server.py", line 17, in tcp_server
    cli.recv(1024)
BlockingIOError: [Errno 11] Resource temporarily unavailable

可以看到,在服务端 recv(1024)时,触发了 [Errno 11]

3.3、send() 触发

send() 触发,同样需要一个服务端和一个客户端。当服务端接收客户端连接后,不及时 recv() 客户端的消息,在 while 循环中 sleep()。这会导致客户端多次发送消息后,在 send() 函数触发异常。

服务端代码,如下:

# _*_ coding:utf-8 _*_
"""
tcp服务端
"""
import socket
import time


def tcp_server():
    server = socket.socket()
    address = ('0.0.0.0', 80)
    server.bind(address)
    server.listen(3)
    cli, addr = server.accept()

    while 1:
        time.sleep(10)


if __name__ == '__main__':
    tcp_server()

客户端代码,如下:

# _*_ coding:utf-8 _*_
"""
tcp客户端
"""
import socket

def tcp_client():
    ip_port = ('127.0.0.1', 80)
    client = socket.socket()
    client.connect(ip_port)
    
    # 设置为非阻塞
    client.setblocking(False)

    while 1:
        client.send('hello world!'.encode())
        print(f'send data success.')



if __name__ == '__main__':
    tcp_client()

首先,运行服务端程序;然后,运行客户端程序,访问服务端。

客户端在多次 send() 后,报错如下:

    .
    .
    .
send data success.
send data success.
send data success.
Traceback (most recent call last):
  File "tcp_client.py", line 20, in <module>
    tcp_client()
  File "tcp_client.py", line 14, in tcp_client
    client.send('hello world!'.encode())
BlockingIOError: [Errno 11] Resource temporarily unavailable

可以看到,客户端在很多次 send() 后,触发了 [Errno 11]

四、原因分析

acceptrecv 的触发来看,“答案二”似乎更正确,即:

“在非阻塞模式下调用了阻塞操作,在该操作没有完成就返回这个错误”。

但是,从 send 的触发来看,send 了很多次后,才会触发 [Errno 11]。“答案一”好像更有道理,即:

“由于是异步的,write\send 将要发送的数据提交到发送缓冲区后是立即返回的,并不需要对端确认数据已接收。在这种情况下是很有可能出现发送缓冲区被填满,导致 write\send 无法再向缓冲区提交要发送的数据。因此就产生了 Resource temporarily unavailable 的错误。”。

两种答案看着都有自己的道理,但我还是没明白,它们和“资源暂时不可用”有什么关联。

为什么 EAGAIN 异常给出的提示信息是“[Errno 11] Resource temporarily unavailable”呢?

继续研究分析后,我得出了自己的结论,依然按照accept()recv()send()三个函数来分析说明。

4.1、accept() 分析

当我们创建一个 socket 监听某个端口后,所有完成三次握手的客户端连接,都会按照到达先后顺序被放入主线 socket等待连接队列中。先放入的先被取出

socket 执行 listen() 后,可以调用 accept() 函数从等待连接队列中取出一个连接请求,并创建一个新的 socket 用于与客户端通信,然后返回。

阻塞模式下,主线 socket 调用 accept 后,如果等待队列中没有新的请求,就会一直阻塞,直到可以从队列中取出新的请求才返回。

非租塞模式下,如果等待队列中没有可取的连接,accept() 也会立马返回,并抛出 BlockingIOError: [Errno 11] Resource temporarily unavailable 异常。

此时,我理解的资源不可用,是指等待连接队列中没有数据可取!

如果,等待连接队列中有新连接可取,阻塞模式和非阻塞模式下的 accept() 是没有区别的。

4.2、recv() 分析

socekt 调用 recv() 接收消息时,并不是直接从对端 socket 获取数据,而是从接收缓冲区读取数据。

阻塞模式下,如果接收缓冲区没有数据可读,recv() 会一直阻塞,直到有数据可读。

非阻塞模式下,recv() 从接收缓冲区读取数据时,如果没有数据,也会立马返回,并抛出 BlockingIOError: [Errno 11] Resource temporarily unavailable 异常。

此时,我理解的资源不可用,是指接收缓冲区没有数据可读!

如果,接收缓冲区中有数据可读,阻塞模式和非阻塞模式下的 recv() 是没有区别的。

4.3、send() 分析

socket 使用 send() 方法发送数据时,会先将数据提交到发送缓冲区,由内核通过网络发送出去。

在阻塞模式下,send() 操作会等待所有数据均被复制到发送缓冲区后才会返回。

例如,如果发送缓冲总大小为 1024,现在已经复制了 1023 个字节到发送缓冲区,那么剩余的可用发送缓冲区大小为 1 字节。如果继续发送数据,执行下一个 send() 操作,并且 send 的数据长度大于 1。此时,send() 中的待发送数据是无法全部被写入到发送缓冲区的,send() 将会阻塞,直到内核取走发送缓冲区中部分数据,send() 的所有数据全部被写入发送缓冲区后才返回。

使用 sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) 可以查看 socket 的 发送缓冲区大小。

非阻塞模式下,send() 发送数据时,如果发送缓冲区可用大小不足以支持 send() 写入全部数据,send() 方法也会立马返回,并抛出 BlockingIOError: [Errno 11] Resource temporarily unavailable 异常。

通过前面 send() 方法触发异常的代码,可以看到在服务端一直没有读取网络数据,导致客户端发送缓冲区中数据处理不及时,而客户端又在循环发送大量数据,当发送缓冲区被写满后,继续发送数据触发了资源不可用异常。

此时,我理解的资源不可用,是指发送缓冲区没有足够空间可用!

如果,发送缓冲区中还有足够空间,允许send() 函数提交指定的所有数据,阻塞模式和非阻塞模式下的 send() 是没有区别的。

五、EAGAIN 处理

知道触发 EAGAIN 的原因后,处理就比较简单了。

对非阻塞 socket 而言,EAGAIN 其实不能算是真正的错误。抛出 EAGAIN 异常,只是想告诉我们稍后再试。

我将 EAGAIN 的处理分为两种情况。

5.1、accept()recv() 处理

对于 accept()recv() 引起的 EAGAIN 异常,我们可以直接捕获异常,然后 pass 掉。

# accept()处理
try:
    client, address = sock.accept()
except BlockingIOError as err:
    pass

# recv()处理
try:
    data = sock.recv(1024)
except BlockingIOError as err:
    pass
    

这样处理,不用担心会漏掉某些连接或数据接收。下次循环,接着 accept()recv(),不会有任何影响。

5.2、send() 处理

对于 send() 引起的 EAGAIN 异常,不能直接 pass。作为数据发送方,直接 passEAGAIN 会导致数据丢失。

我们可以,尝试 sleep(1),给内核足够的时间取走发送缓冲区中的数据,然后再次尝试发送。

try:
    sock.send('hello world!'.encode())
except BlockingIOError as err:
    time.sleep(1)
    sock.send('hello world!'.encode())

这样的处理方式虽然简单,但不是最好的。如果接收方程序存在异常,导致接收方 recv() 的速度远远小于发送方 send() 的速度。那么,可能 sleep(1) 会导致我们的程序性能急剧下降。

比较好的做法是结合 pollepoll 等,暂时保存下发送失败的数据,将对应 socket 丢入事件循环中,等待可写事件触发,再次发送。

事件循环的示例写出会比较长,但是不难,感兴趣的朋友可以研究研究~~ 如果有任何问题,欢迎讨论。

tip:以上分析,如有问题,欢迎指正!

END.

原创不易,点个赞呗!

如果喜欢,欢迎随意赞赏,动力支援,请作者喝奶茶

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python3「非阻塞socket」报错 “BlockingIOError: [Errno 11]“ 复现以及分析解决 的相关文章

  • 使用 pythonbrew 编译 Python 3.2 和 2.7 时出现问题

    我正在尝试使用构建多个版本的 python蟒蛇酿造 http pypi python org pypi pythonbrew 0 7 3 但我遇到了一些测试失败 这是在运行的虚拟机上 Ubuntu 8 04 32 位 当我使用时会发生这种情
  • 没有名为 crypto.cipher 的模块

    我现在正在尝试加密一段时间 我最近得到了这个基于 python 的密码器 名为PythonCrypter https github com jbertman PythonCrypter 我对 Python 相当陌生 当我尝试通过终端打开 C
  • 通过 Scrapy 抓取 Google Analytics

    我一直在尝试使用 Scrapy 从 Google Analytics 获取一些数据 尽管我是一个完全的 Python 新手 但我已经取得了一些进展 我现在可以通过 Scrapy 登录 Google Analytics 但我需要发出 AJAX
  • 在 python 程序中合并第三方库的最佳实践是什么?

    下午好 我正在为我的工作编写一个中小型Python程序 该任务需要我使用 Excel 库xlwt and xlrd 以及一个用于查询 Oracle 数据库的库 称为CX Oracle 我正在通过版本控制系统 即CVS 开发该项目 我想知道围
  • 将数据从 python pandas 数据框导出或写入 MS Access 表

    我正在尝试将数据从 python pandas 数据框导出到现有的 MS Access 表 我想用已更新的数据替换 MS Access 表 在 python 中 我尝试使用 pandas to sql 但收到错误消息 我觉得很奇怪 使用 p
  • OpenCV Python cv2.mixChannels()

    我试图将其从 C 转换为 Python 但它给出了不同的色调结果 In C Transform it to HSV cvtColor src hsv CV BGR2HSV Use only the Hue value hue create
  • Python(Selenium):如何通过登录重定向/组织登录登录网站

    我不是专业程序员 所以请原谅任何愚蠢的错误 我正在做一些研究 我正在尝试使用 Selenium 登录数据库来搜索大约 1000 个术语 我有两个问题 1 重定向到组织登录页面后如何使用 Selenium 登录 2 如何检索数据库 在我解决
  • 如何使用 Ansible playbook 中的 service_facts 模块检查服务是否存在且未安装在服务器中?

    我用过service facts检查服务是否正在运行并启用 在某些服务器中 未安装特定的软件包 现在 我如何知道这个特定的软件包没有安装在该特定的服务器上service facts module 在 Ansible 剧本中 它显示以下错误
  • 如何替换 pandas 数据框列中的重音符号

    我有一个数据框dataSwiss其中包含瑞士城市的信息 我想用普通字母替换带有重音符号的字母 这就是我正在做的 dataSwiss Municipality dataSwiss Municipality str encode utf 8 d
  • python 相当于 R 中的 get() (= 使用字符串检索符号的值)

    在 R 中 get s 函数检索名称存储在字符变量 向量 中的符号的值s e g X lt 10 r lt XVI s lt substr r 1 1 X get s 10 取罗马数字的第一个符号r并将其转换为其等效整数 尽管花了一些时间翻
  • 如何加速Python中的N维区间树?

    考虑以下问题 给定一组n间隔和一组m浮点数 对于每个浮点数 确定包含该浮点数的区间子集 这个问题已经通过构建一个解决区间树 https en wikipedia org wiki Interval tree 或称为范围树或线段树 已经针对一
  • 绘制方程

    我正在尝试创建一个函数 它将绘制我告诉它的任何公式 import numpy as np import matplotlib pyplot as plt def graph formula x range x np array x rang
  • 如何在ipywidget按钮中显示全文?

    我正在创建一个ipywidget带有一些文本的按钮 但按钮中未显示全文 我使用的代码如下 import ipywidgets as widgets from IPython display import display button wid
  • 使用 \r 并打印一些文本后如何清除控制台中的一行?

    对于我当前的项目 有一些代码很慢并且我无法使其更快 为了获得一些关于已完成 必须完成多少的反馈 我创建了一个进度片段 您可以在下面看到 当你看到最后一行时 sys stdout write r100 80 n I use 80覆盖最终剩余的
  • 解释 Python 中的数字范围

    在 Pylons Web 应用程序中 我需要获取一个字符串 例如 关于如何做到这一点有什么建议吗 我是 Python 新手 我还没有找到任何可以帮助解决此类问题的东西 该列表将是 1 2 3 45 46 48 49 50 51 77 使用
  • 有没有办法检测正在运行的代码是否正在上下文管理器内执行?

    正如标题所述 有没有办法做到这样的事情 def call back if called inside context print running in context else print called outside context 这将
  • 使用 Python 绘制 2D 核密度估计

    I would like to plot a 2D kernel density estimation I find the seaborn package very useful here However after searching
  • Python:如何将列表列表的元素转换为无向图?

    我有一个程序 可以检索 PubMed 出版物列表 并希望构建一个共同作者图 这意味着对于每篇文章 我想将每个作者 如果尚未存在 添加为顶点 并添加无向边 或增加每个合著者之间的权重 我设法编写了第一个程序 该程序检索每个出版物的作者列表 并
  • 如何计算 pandas 数据帧上的连续有序值

    我试图从给定的数据帧中获取连续 0 值的最大计数 其中包含来自 pandas 数据帧的 id date value 列 如下所示 id date value 354 2019 03 01 0 354 2019 03 02 0 354 201
  • 如何将输入读取为数字?

    这个问题的答案是社区努力 help privileges edit community wiki 编辑现有答案以改进这篇文章 目前不接受新的答案或互动 Why are x and y下面的代码中使用字符串而不是整数 注意 在Python 2

随机推荐

  • Debian12中为python3配置虚拟环境及在Pycharm中使用虚拟环境

    在Debian 12中 python默认为python 3 11 基于应用 现需设置虚拟环境 1 安装venv模块 从python3 3开始 配置python虚拟环境 可用venv模块 更加方便了 执行命令 apt install pyth
  • 网络安全管理

    网络安全面临的主要威胁 人为因素 系统和运行环境等 常见的互联网服务安全包括 Web浏览器安全 文件传输 FTP 服务安全 E mail服务安全 远程登录 Telnet 安全 DNS域名安全和设备的实体安全 防火墙的局限性以及风险 防火墙能
  • 编译和安装gdb源码详细步骤介绍

    1 gdb源码下载 1 源码下载网址 https ftp gnu org gnu gdb 2 本文下面的编译是按照8 2版本的源码进行的 其余版本的源码可能会报错 需要自行解决 2 编译源码 2 1 Makefile文件 顶层目录 TOOL
  • 银行业法律法规与综合能力 第四章 银行从业法律基础 25%

    第四章 银行从业法律基础 4 1 银行基本法律法规 1 考点1 中国人民银行的职能和职责 一 职能 二 职责 考点2 中国人民银行的监督管理 一 直接检查监督杈 二 建议检查监督杈 三 特定情况下的检查监督权 考点3 国务院银行业监督管理机
  • hexo引用本地图片无法显示

    最近重新开始用起hexo 但是发现在文章中引用本地图片时总是显示不出来 问题如下图所示 花费了许久时间才解决这个问题 因此将一些解决经验整理出来 希望能帮助到大家 一 插件安装与配置 首先我们需要安装一个图片路径转换的插件 这个插件名字是h
  • 2023年智慧农业与经济发展国际研讨会议(ISSAED 2023)

    2023 International Seminar on Smart Agriculture and Economic Development 地点 合肥 智慧农业 农业信息管理系统 农业物联网系统集成与实践技术 农业大数据分析与应用 农
  • LLVM学习入门(2):实现解析器 Parser 和语法树 AST

    实现解析器 Parser 和语法树 AST 2 1 The Abstract Syntax Tree AST 语法抽象树 2 2 Parser Basics 基本的解析器 2 3 Basic Expression Parsing 基本表达式
  • 计算机与不确定性原理,不确定性原理

    题目 A simple baseline for bayesian uncertainty in deep learning 摘要 本文提出了一种简单 可扩展 通用的面向深度学习的不确定性表示和标定方法SWA Gaussian SWAG 随
  • SCILAB-自由科学计算软件

    SCILAB 自由科学计算软件 原创 2006 04 03 15 05 15 发表者 phoenixlin SCILAB是由法国国家信息与自动化研究院 INRIA 的科学家们开发的 开放源码 科学计算自由软件 SCILAB一词来源于英文 S
  • Graphics2D绘制图片,线段、矩形、圆形

    新建图片 BufferedImage newImage new BufferedImage 1079 512 BufferedImage TYPE INT RGB 获取绘图对象 Graphics2D g2d newImage createG
  • 访问云服务器文件共享,访问云服务器文件共享

    访问云服务器文件共享 内容精选 换一换 华为云帮助中心 为用户提供产品简介 价格说明 购买指南 用户指南 API参考 最佳实践 常见问题 视频帮助等技术文档 帮助您快速上手使用华为云服务 安装传输工具在本地主机和Windows云服务器上分别
  • 数组工具类

    该工具类有两个方法 1 isContained方法用来判断一个数组中是否包含另一个数组中所有的数据 2 arrayDiff方法用来删除一个数组中与另一个数组中值相同的元素 arrUtil js文件 key存在时表示是对象数据 可以不存在时表
  • Flying to the Mars(字典树)

    Flying to the Mars Time Limit 5000 1000 MS Java Others Memory Limit 32768 32768 K Java Others Total Submission s 12965 A
  • 整形在内存中的存储

    目录 整形在内存中的存储 大小端字节序存储 什么是大端小端 判断大小端的代码 变量的创建是要在内存中开辟空间的 空间的大小是根据不同的类型而决定的 那接下来我们谈谈数据在所开辟内存中到底是如何存储的 整形在内存中的存储 计算机中的整数有三种
  • UE4 伤害事件,不同部位不同伤害(C++)

    UE4 伤害事件 不同部位不同伤害 C 可以先看射线检测 效果 打头和身体有不同的伤害 前面设置部分 先设置项目设置里的物理的Physical Surface 添加好身体的部位 2 添加了几个就几个变量 设置好它们的表面类型 3 找到被伤害
  • 第1章 数据库系统概论---数据库原理及应用

    目录 课程学习目标 本课程教学内容 课程教材 课程实践使用的数据库软件 第1章 数据库系统概论 1 数据库系统概述 一 基本概念 数据 文字 图片等数据化后存入计算机 数据库 DB 按一定的数据模型组织的共享数据 数据库管理系统 DBMS
  • python 读写hive

    最近正在 做一个 项目 需要把 算法模型的结果持久化 至hive 目前 使用的 pyhive 切记 在windows上不能使用 我目前在centos6 5上使用 官方说再macos和linux上可用 from pyhive import h
  • Vue中filter函数 过滤器的使用

    filters是什么 filters顾名思义是一个过滤器 就是对数据进行过滤筛选 将数据转化为我们想要的格式 但是他不会改变原始数据 filters分为两类 一 局部过滤器 局部过滤器的特点 只能作用于本组件没内 定义局部过滤器 在本组件内
  • Flutter和Android中的View

    在Android中 View是屏幕上显示的所有内容的基础 按钮 工具栏 输入框等一切都是View 在Flutter中 View相当于是Widget 然而 与View相比 Widget有一些不同之处 首先 Widget仅支持一帧 并且在每一帧
  • python3「非阻塞socket」报错 “BlockingIOError: [Errno 11]“ 复现以及分析解决

    梦想还在 生活当继续 一 前言 linux 下 用 python 的非阻塞 socket 通信时 遇到了 BlockingIOError Errno 11 Resource temporarily unavailable 错误 翻译报错信息