聊一聊 .NET在Linux下的IO多路复用select和epoll
一:背景
1. 讲故事
在windows平台上,相信很多人都知道.NET异步机制是借助了Windows自带的 IO完成端口
实现的异步交互,那在 Linux 下.NET 又是怎么玩的呢?主要还是传统的 select,poll,epoll 的IO多路复用,在 coreclr源代码中我们都能找到它们的影子。
- select & poll
在平台适配层的 pal.cpp
文件中,有这样的一句话。
#if HAVE_POLL
#include <poll.h>
#else
#include "pal/fakepoll.h"
#endif // HAVE_POLL
简而言之就是在不支持 poll 的linux版本中使用 select(fakepoll) 模拟,参考代码如下:
- epoll
同样的在 linux 中你也会发现很多,截图如下:
二:select IO多路复用
1. select 解读
在没有 select 之前,我们需要手工管理多句柄的收发,在使用select IO多路复用技术之后,这些多句柄管理就由用户转交给linux系统了,这个也可以从核心的 select
函数看出。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- readfds,writefds,exceptfds
这三个字段依次监视着哪些句柄已成可读状态,哪些句柄已成可写状态,哪些句柄已成异常状态,那技术上是如何实现的呢?在libc 中定义了一个 bit 数组,刚好文件句柄fd值
作为 bit数组的索引,linux 在内核中只需要扫描 __fds_bits 中哪些位为1 即可找到需要监控的句柄。
/* fd_set for select and pselect. */
typedef struct
{
/* XPG4.2 requires this member name. Otherwise avoid the name
from the global namespace. */
#ifdef __USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
} fd_set;
- nfds,timeout
为了减少扫描范围,提高程序性能,需要用户指定一个最大的扫描值到 nfds 上。后面的timeout即超时时间。
2. select 的一个小例子
说了再多还不如一个例子有说服力,我们使用 select 机制对 Console 控制台句柄 (STDIN_FILENO) 进行监控,一旦有数据进来立马输出,参考代码如下:
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
int main()
{
fd_set readfds;
struct timeval timeout;
char buf[256];
printf("Enter text (press Ctrl+D to end):\n");
while (1)
{
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
timeout.tv_sec = 5; // 5秒超时
timeout.tv_usec = 0;
int ready = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &timeout);
if (ready == -1)
{
perror("select");
break;
}
else if (ready == 0)
{
printf("\nTimeout (5秒无输入).\n");
break;
}
else if (FD_ISSET(STDIN_FILENO, &readfds))
{
// 使用 fgets 逐行读取
if (fgets(buf, sizeof(buf), stdin) != NULL)
{
printf("You entered: %s", buf); // 输出整行(包含换行符)
}
else
{
printf("\nEnd of input (Ctrl+D pressed).\n");
break;
}
}
}
return 0;
}
稍微解释下代码逻辑。
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
- 将 STDIN_FILENO=0 塞入到可读句柄监控 (readfds) 中。
- 数据进来之后 select 被唤醒,执行后续逻辑。
- 通过 FD_ISSET 判断 bit=0 的位置(STDIN_FILENO)是否可用,可用的话读取数据。
如果大家对 select 底层代码感兴趣,可以看下 linux 的 do_select
简化实现,大量的遍历逻辑(bit)。
static noinline_for_stack int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
{
for (;;) {
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
bool can_busy_loop = false;
inp = fds->in; outp = fds->out; exp = fds->ex;
rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
in = *inp++; out = *outp++; ex = *exp++;
all_bits = in | out | ex;
for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {
mask = select_poll_one(i, wait, in, out, bit,busy_flag);
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
wait->_qproc = NULL;
}
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
wait->_qproc = NULL;
}
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
wait->_qproc = NULL;
}
}
}
if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack))
timed_out = 1;
}
return retval;
}
三:epoll IO多路复用
1. epoll 解读
现在主流的软件(Redis,Nigix) 都是采用 epoll,它解决了select低效的遍历,毕竟数组最多支持1024个bit位,一旦句柄过多会影响异步读取的效率。epoll的底层借助了。
- 红黑树:对句柄进行管理,复杂度为 O(logN)。
- 就绪队列:一旦句柄变得可读或可写,内核会直接将句柄送到就绪队列。
libc中使用 epoll_wait
函数监视着就绪队列,一旦有数据立即提取,复杂度 O(1),其实这个机制和 Windows 的IO完成端口 已经很靠近了,最后配一下参考代码。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_EVENTS 10 // 最大监听事件数
#define TIMEOUT_MS 5000 // epoll_wait 超时时间(毫秒)
int main()
{
int epoll_fd, nfds; // epoll 文件描述符和返回的事件数
struct epoll_event ev, events[MAX_EVENTS]; // epoll 事件结构体
char buf[256];
// 创建 epoll 实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1)
{
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 配置并添加标准输入到 epoll 监听
ev.events = EPOLLIN; // 监听文件描述符的可读事件(输入)
ev.data.fd = STDIN_FILENO; // 监听标准输入(文件描述符 0)
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1)
{
perror("epoll_ctl: STDIN_FILENO");
exit(EXIT_FAILURE);
}
printf("Enter text line by line (press Ctrl+D to end):\n");
// 主循环:监听事件
while (1)
{
// 等待事件发生或超时
nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, TIMEOUT_MS);
if (nfds == -1)
{
perror("epoll_wait");
break;
}
else if (nfds == 0)
{
printf("\nTimeout (5秒无输入).\n");
break;
}
// 处理所有触发的事件
for (int n = 0; n < nfds; ++n)
{
if (events[n].data.fd == STDIN_FILENO)
{
// 使用 fgets 逐行读取输入
if (fgets(buf, sizeof(buf), stdin) != NULL)
{
printf("You entered: %s", buf);
}
else
{
// 输入结束(用户按下 Ctrl+D)
printf("\nEnd of input (Ctrl+D pressed).\n");
break;
}
}
}
}
close(epoll_fd);
return 0;
}
四:总结
说了这么多,文尾总结下目前主流的 epoll 和 iocp 各自的特点。
特性 | epoll (Linux) | IOCP (Windows) |
---|---|---|
模型 | 事件驱动 (Reactor) | 完成端口 (Proactor) |
核心思想 | 通知可读写事件 | 通知I/O操作完成 |
适用场景 | 高并发网络编程 | 高并发I/O操作 |
编程复杂度 | 较低 | 较高 |
网络I/O性能 | 极佳(百万级连接) | 优秀 |
磁盘I/O支持 | 有限 | 完善 |
CPU利用率 | 高 | 中 |
内存开销 | 低 | 中 |
![]() |
聊一聊 .NET在Linux下的IO多路复用select和epoll的更多相关文章
- linux网络编程 IO多路复用 select epoll
本文以我的小型聊天室为例,对于服务器端的代码,做了三次改进,我将分别介绍阻塞式IO,select,epoll . 一:阻塞式IO 对于聊天室这种程序,我们最容易想到的是在服务器端accept之后,然后 ...
- 转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】
下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架, ...
- IO多路复用select/poll/epoll详解以及在Python中的应用
IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...
- Python异步非阻塞IO多路复用Select/Poll/Epoll使用,线程,进程,协程
1.使用select模拟socketserver伪并发处理客户端请求,代码如下: import socket import select sk = socket.socket() sk.bind((' ...
- IO 多路复用 select/poll/epoll ---> Reactor ---> Netty
什么是内核态和用户态 https://blog.csdn.net/qq_41709234/article/details/124320482 参考:https://www.cnblogs.com/lo ...
- Linux IO多路复用 select/poll/epoll
Select -- synchronius I/O multiplexing select, FS_SET,FD_CLR,FD_ISSET,FD_ZERO #include <sys/time. ...
- 最快理解 - IO多路复用:select / poll / epoll 的区别.
目录 第一个解决方案(多线程) 第二个解决方案(select) 第三个解决方案(poll) 最终解决方案(epoll) 客栈遇到的问题 从开始学习编程后,我就想开一个 Hello World 餐厅,由 ...
- python网络编程——IO多路复用select/poll/epoll的使用
转载博客: http://www.haiyun.me/archives/1056.html http://www.cnblogs.com/coser/archive/2012/01/06/231521 ...
- linux 下 I/O 多路复用初探
本文内容整理自B站up主 free-coder 发布的视频:[并发]IO多路复用select/poll/epoll介绍 引入 一般来讲,服务器在处理IO请求(一般指的是socket编程)时,需要对so ...
- Linux 网络编程的5种IO模型:多路复用(select/poll/epoll)
Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 背景 我们在上一讲 Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO中,对于其中的 阻塞/非阻塞IO 进行了 ...
随机推荐
- k8s DCGM GPU采集指标项说明
dcgm-exporter 采集指标项 https://help.aliyun.com/document_detail/433222.html#section-oin-6mf-6j0 指标 解释 dc ...
- React.memo 解决函数组件重复渲染
为什么会存在重复渲染? react 在 v16.8 版本引入了全新的 api,叫做 React Hooks,它的使用与以往基于 class component 的组件用法非常的不一样,不再是基于类,而 ...
- Django项目如何配置日志文件信息
1.以dict的方式配置在settings.py中 # 日志文件简单配置 ''' LOGGING = { "version": 1, "disable_existing_ ...
- 远程服务器画面GUI传输到本地显示
远程服务器画面GUI传输到本地显示 这篇文章主要介绍远程服务器(Ubuntu系统)的GUI画面传输到本地(本地Ubuntu或者Windows)显示的方法. 远程Ubuntu系统 --> 本地Wi ...
- EntityFrameworkCore 分页问题
场景重现 使用 EntityFrameworkCore 连接 SQL Server 2008 执行.Skip().Take()分页查询时出现如下异常: SqlException: 'OFFSET' 附 ...
- tesseract引擎RVV代码学习笔记
Tesseract 是一个开源的 OCR(Optical Character Recognition,光学字符识别)引擎,可将图像中的文本转换为机器可读的文本格式.由于组内曾经有同事为这个项目贡献 ...
- Hello, PostgreSQL
Hello, PostgreSQL Whoami:5年+金融.政府.医疗领域工作经验的DBA Certificate:PGCM.OCP.YCP Skill:Oracle.Mysql.PostgreSQ ...
- 剖析 Docker Swarm 操作对容器端口影响
剖析 Docker Swarm 操作对容器端口影响 一.背景阐述 在使用 Docker Swarm 构建集群环境过程中,于 ts3 节点出现了原有的容器端口全部失效,手动重启后才恢复的情况.期间涉及 ...
- javaWeb基础之Tomcat
一.Tomcat:web服务器软件 1. 下载:http://tomcat.apache.org/ 2. 安装:解压压缩包即可. * 注意:安装目录建议不要有中文和空格 3. 卸载:删除目录就行了 4 ...
- 🎀Docker下Dubbo服务优雅上下线实现
简介 在Docker容器环境中部署基于Dubbo的服务时,实现服务的优雅上下线是至关重要的.这通常涉及到两个关键步骤:首先,确保服务能够从注册中心摘除,停止接受新的请求:其次,等待所有正在处理的请求完 ...