揭秘网络编程:同步与异步IO模型的实战演练
摘要
在网络编程领域,同步(Synchronous)、异步(Asynchronous)、阻塞(Blocking)与非阻塞(Non-blocking)IO模型是核心概念。尽管这些概念在多篇文章中被广泛讨论,它们的抽象性使得彻底理解并非易事。本文旨在通过具体的实验案例,将这些抽象概念具体化,以助于读者构建清晰的理解框架。
概念
IO 复用到底复用了什么
IO 的类型有网络 IO、磁盘 IO。我们可以把标准输入、套接字等都看做 I/O 的一路,多路复用的意思,就是在任何一路 I/O 有“事件”发生的情况下,通知应用程序去处理相应的 I/O 事件,这样我们的程序就变成了“多面手”,在同一时刻仿佛可以处理多个 I/O 事件。
IO 事件类型
- 标准输入文件描述符准备好可以读。
- 监听套接字准备好,新的连接已经建立成功。
- 已连接套接字准备好可以写。
- 如果一个 I/O 事件等待超过了 10 秒,发生了超时事件。
IO 模型
阻塞 IO
阻塞 IO 模型,当我们调用 recvfrom 读取数据时,只用等数据完全准备好,然后应用程序把数据从内核态拷贝到应用空间,程序才会返回,否则从调用方视角来看程序将会一直阻塞在 recvfrom 上。
非阻塞 IO
非阻塞IO场景发起 recvfrom 后,在内核数据没准备好的情况下会返回 EWOULDBLOCK,EAGAIN 错误,所以调用方需要不断的轮训获取数据结果。非阻塞 I/O 可以使用在 read、write、accept、connect 等多种不同的场景,在非阻塞 I/O 下,使用轮询的方式引起 CPU 占用率高,所以一般将非阻塞 I/O 和 I/O 多路复用技术 select、poll 等搭配使用,在非阻塞 I/O 事件发生时,再调用对应事件的处理函数。这种方式,极大地提高了程序的健壮性和稳定性,是 Linux 下高性能网络编程的首选。
非阻塞 IO Write 流程
/* 向文件描述符 fd 写入 n 字节数 */
ssize_t writen(int fd, const void * data, size_t n)
{size_t nleft;ssize_t nwritten;const char *ptr;ptr = data;nleft = n;// 如果还有数据没被拷贝完成,就一直循环while (nleft > 0) {if ( (nwritten = write(fd, ptr, nleft)) <= 0) {/* 这里 EINTR 是非阻塞 non-blocking 情况下,通知我们再次调用 write() */if (nwritten < 0 && errno == EINTR)nwritten = 0; elsereturn -1; /* 出错退出 */}/* 指针增大,剩下字节数变小 */nleft -= nwritten;ptr += nwritten;}return n;
}
- nleft 标记剩余写入数据
- while 循环一直写入直到 nleft == 0
- write 失败后,如果是非阻塞将会返回 EINTR 错误,说明数据还未准备好,这是我们将 nwritten 置为0
- write 成功则说明内核 socket 缓冲有空间了,不断写入值直到 nleft == 0
IO 复用
IO 复用不同于非阻塞IO的地方在于,IO 复用是在内核态实现了轮训,相比应用层实现少了很多系统调用(系统调用成本很高)
Read 和 Write 非阻塞模式对比
图源-网络编程实战
案例
使用Select与非阻塞IO实现高效网络通信
#define MAX_LINE 1024
#define FD_INIT_SIZE 128char rot13_char(char c) {if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))return c + 13;else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))return c - 13;elsereturn c;
}// 数据缓冲区
struct Buffer {int connect_fd; // 连接字char buffer[MAX_LINE]; // 实际缓冲size_t writeIndex; // 缓冲写入位置size_t readIndex; // 缓冲读取位置int readable; // 是否可以读
};struct Buffer *alloc_Buffer() {struct Buffer *buffer = malloc(sizeof(struct Buffer));if (!buffer)return NULL;buffer->connect_fd = 0;buffer->writeIndex = buffer->readIndex = buffer->readable = 0;return buffer;
}void free_Buffer(struct Buffer *buffer) {free(buffer);
}int onSocketRead(int fd, struct Buffer *buffer) {char buf[1024];int i;ssize_t result;// 循环读取数据直到读完while (1) {result = recv(fd, buf, sizeof(buf), 0);if (result <= 0)break;for (i = 0; i < result; ++i) {if (buffer->writeIndex < sizeof(buffer->buffer))buffer->buffer[buffer->writeIndex++] = rot13_char(buf[i]);if (buf[i] == '\n') {buffer->readable = 1; // 缓冲区可以读}}}if (result == 0) {return 1;} else if (result < 0) {if (errno == EAGAIN)return 0;return -1;}return 0;
}int onSocketWrite(int fd, struct Buffer *buffer) {while (buffer->readIndex < buffer->writeIndex) {ssize_t result = send(fd, buffer->buffer + buffer->readIndex, buffer->writeIndex - buffer->readIndex, 0);if (result < 0) {if (errno == EAGAIN)return 0;return -1;}buffer->readIndex += result;}if (buffer->readIndex == buffer->writeIndex)buffer->readIndex = buffer->writeIndex = 0;buffer->readable = 0;return 0;
}int main(int argc, char **argv) {int listen_fd;int i, maxfd;struct Buffer *buffer[FD_INIT_SIZE];for (i = 0; i < FD_INIT_SIZE; ++i) {buffer[i] = alloc_Buffer();}// 设置 非 阻塞监听listen_fd = tcp_nonblocking_server_listen(SERV_PORT);fd_set readset, writeset, exset;FD_ZERO(&readset);FD_ZERO(&writeset);FD_ZERO(&exset);while (1) {maxfd = listen_fd;FD_ZERO(&readset);FD_ZERO(&writeset);FD_ZERO(&exset);// listener 加入 readsetFD_SET(listen_fd, &readset);for (i = 0; i < FD_INIT_SIZE; ++i) {if (buffer[i]->connect_fd > 0) {if (buffer[i]->connect_fd > maxfd)maxfd = buffer[i]->connect_fd;FD_SET(buffer[i]->connect_fd, &readset);if (buffer[i]->readable) {FD_SET(buffer[i]->connect_fd, &writeset);}}}if (select(maxfd + 1, &readset, &writeset, &exset, NULL) < 0) {error(1, errno, "select error");}if (FD_ISSET(listen_fd, &readset)) {printf("listening socket readable\n");// sleep 模拟处理延时sleep(5);struct sockaddr_storage ss;socklen_t slen = sizeof(ss);// 如果是阻塞 IO 由于超时原因客户端断开连接,此时服务端的连接也失效,加入一直没有请求进来// 将会一直阻塞在 accept 这里。如果是异步IO accept 将会立刻返回,但我们要处理好 accept 的// 异常情况int fd = accept(listen_fd, (struct sockaddr *) &ss, &slen);if (fd < 0) {error(1, errno, "accept failed");} else if (fd > FD_INIT_SIZE) {error(1, 0, "too many connections");close(fd);} else {// 把连接套接字设置为非阻塞make_nonblocking(fd);if (buffer[fd]->connect_fd == 0) {buffer[fd]->connect_fd = fd;} else {error(1, 0, "too many connections");}}}for (i = 0; i < maxfd + 1; ++i) {int r = 0;if (i == listen_fd)continue;if (FD_ISSET(i, &readset)) {r = onSocketRead(i, buffer[i]);}if (r == 0 && FD_ISSET(i, &writeset)) {r = onSocketWrite(i, buffer[i]);}if (r) {buffer[i]->connect_fd = 0;close(i);}}}
}
- 调用 fcntl 将监听套接字设置为非阻塞。
- 行调用 select 进行 I/O 事件分发处理
- 把accept的连接套接字设置为非阻塞的
- 处理连接套接字上的 I/O 读写事件,抽象了一个 Buffer 对象,Buffer 对象使用了 readIndex 和 writeIndex 分别表示当前缓冲的读写位置。
结尾
文章总结了同步、异步、阻塞与非阻塞IO模型的关键概念,并通过对Select与非阻塞IO的案例分析,展示了这些概念在实际编程中的应用。希望读者通过本文能够获得对网络编程中IO模型的深入理解,并指导实践中的应用
Reference
- http://www.pandademo.com/2016/11/linux-kernel-select-source-dissect/
- https://www.jianshu.com/p/95b50b026895
- https://www.zhihu.com/question/19732473
- https://tubetrue01.github.io/articles/2021/08/16/c_unix/Socket(%E4%BA%8C)recv%E4%B8%8Esend%E5%87%BD%E6%95%B0/
- https://time.geekbang.org/column/intro/100032701
- https://github.com/froghui/yolanda
相关文章:

揭秘网络编程:同步与异步IO模型的实战演练
摘要 在网络编程领域,同步(Synchronous)、异步(Asynchronous)、阻塞(Blocking)与非阻塞(Non-blocking)IO模型是核心概念。尽管这些概念在多篇文章中被广泛讨论,它们的抽象性使得彻底理解并非易事。本文旨在通过具体的实验案例,将这些抽象…...

在Visual Studio Code和Visual Studio 2022下配置Clang-Format,格式化成Google C++ Style
项目开发要求好的编写代码格式规范,常用的是根据Google C Style Guide 网上查了很多博文,都不太一样有的也跑不起来,通过尝试之后,自己可算折腾好了,整理一下过程 背景: 编译器主要有三部分:前…...

民国漫画杂志《时代漫画》第32期.PDF
时代漫画32.PDF: https://url03.ctfile.com/f/1779803-1248635561-0ae98a?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了,截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!...
RTKLIB学习--前向滤波
#前言 如果要详细了解RTKLIB或进行二次开发,了解obs指针所存储每个历元的卫星观测数据是必不可少的环节,此文对RTKLIB的(由于后处理和实时运行都要用到前向滤波)前向滤波(从文件头读取观测数据到obs结构体中࿰…...

利用C++与Python调用千帆免费大模型,构建个性化AI对话系统
千帆大模型已于2024年4月25日正式免费,调用这个免费的模型以实现自己的AI对话功能,遵循以下步骤: 了解千帆大模型: 千帆大模型是百度智能云推出的一个平台,提供了一系列AI能力和工具,用于快速开发和应用A…...

VTK9.2.0+QT5.14.0绘制三维显示背景
背景 上一篇绘制点云的博文中,使用的vtkCameraOrientationWidget来绘制的坐标轴,最近又学习到两种新的坐标轴绘制形式。 vtkOrientationMarkerWidget vtkAxesActor 单独使用vtkAxesActor能够绘制出坐标轴,但是会随着鼠标操作旋转和平移时…...

Vue.js2+Cesium1.103.0 十六、多模型轨迹运动
Vue.js2Cesium1.103.0 十六、多模型轨迹运动 Demo <template><div id"cesium-container" style"width: 100%; height: 100%;"><ul class"ul"><li v-for"(item, index) of deviceInfo" :key"index" cl…...

Matlab|基于PMU相量测量单元进行电力系统电压幅值和相角状态估计
主要内容 程序采用三种方法对14节点和30节点电力系统状态进行评估: ①PMU同步相量测量单元结合加权最小二乘法(WLS)分析电力系统的电压幅值和相角状态; ②并采用牛顿-拉夫逊方法进行系统潮流计算,结果作为理论分…...

【C++】---二叉搜索树
【C】---二叉搜索树 一、二叉搜索树概念二、二叉搜索树操作(非递归)1.二叉搜索树的查找 (非递归)(1)查找(2)中序遍历 2.二叉搜索树的插入(非递归)3.二叉搜索树…...
FastAPI - 依赖注入3
在FastAPI中,依赖注入是一种强大的功能,它允许你轻松地将依赖项注入到你的路由处理程序函数中,以处理不同的任务,例如数据库访问、认证和配置管理。 FastAPI支持依赖注入通过以下方式: 使用参数注解: 你可…...

【网络运维的重要性】
🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共…...

YOLOv5改进 | 注意力机制 | 添加双重注意力机制 DoubleAttention【附代码/涨点能手】
💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 在图像识别中,学习捕捉长距离关系是基础。现有的CNN模型通常通过增加深度来建立这种关系,但这种形式效率极低。因此&…...

自用网站合集
总览 线上工具-图片压缩 TinyPNG线上工具-url参数解析 线上工具-MOV转GIF UI-Vant微信小程序版本其他-敏捷开发工具 Leangoo领歌 工具 线上工具-图片压缩 TinyPNG 不能超过5m,别的没啥缺点 线上工具-url参数解析 我基本上只用url参数解析一些常用的操作在线…...
【Golang】gin框架如何在中间件中捕获响应并修改后返回
【Golang】gin框架如何在中间件中捕获响应并修改后返回 本文讲述如何捕获中间件响应以及重写响应如果想在中间件中记录响应日志等操作,我们该如何获取响应数据呢?假如需要统一对响应数据做加密,如何修改这个返回数据再响应给客户端呢…...

电脑同时配置两个版本mysql数据库常见问题
1.配置时,要把bin中的mysql.exe和mysqld.exe 改个名字,不然两个版本会重复,当然,在初始化数据库的时候,如果时57版本的,就用mysql57(已经改名的)和mysqld57 代替 mysql 和 mysqld 例如 mysql -u root -p …...

Java | Leetcode Java题解之第112题路径总和
题目: 题解: class Solution {public boolean hasPathSum(TreeNode root, int sum) {if (root null) {return false;}if (root.left null && root.right null) {return sum root.val;}return hasPathSum(root.left, sum - root.val) || has…...

HaloDB 的 Oracle 兼容模式
↑ 关注“少安事务所”公众号,欢迎⭐收藏,不错过精彩内容~ 前倾回顾 前面介绍了“光环”数据库的基本情况和安装办法。 哈喽,国产数据库!Halo DB! 三步走,Halo DB 安装指引 ★ HaloDB是基于原生PG打造的新一代高性能安…...

【Python】解决Python报错:TypeError: ‘xxx‘ object does not support item assignment
🧑 博主简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…...

Spring-注解
Spring 注解分类 Spring 注解驱动模型 Spring 元注解 Documented Retention() Target() // 可以继承相关的属性 Inherited Repeatable()Spirng 模式注解 ComponentScan 原理 ClassPathScanningCandidateComponentProvider#findCandidateComponents public Set<BeanDefin…...

旧手机翻身成为办公利器——PalmDock的介绍也使用
旧手机有吧!!! 破电脑有吧!!! 那恭喜你,这篇文章可能对你有点用了。 介绍 这是一个旧手机废物利用变成工作利器的软件。可以在 Android 手机上快捷打开 windows 上的文件夹、文件、程序、命…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
文章目录 一、开启慢查询日志,定位耗时SQL1.1 查看慢查询日志是否开启1.2 临时开启慢查询日志1.3 永久开启慢查询日志1.4 分析慢查询日志 二、使用EXPLAIN分析SQL执行计划2.1 EXPLAIN的基本使用2.2 EXPLAIN分析案例2.3 根据EXPLAIN结果优化SQL 三、使用SHOW PROFILE…...