揭秘网络编程:同步与异步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 上的文件夹、文件、程序、命…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...