聊一聊 .NET在Linux下的IO多路复用select和epoll

news2025/7/23 8:31:19

一:背景

1. 讲故事

在windows平台上,相信很多人都知道.NET异步机制是借助了Windows自带的 IO完成端口 实现的异步交互,那在 Linux 下.NET 又是怎么玩的呢?主要还是传统的 select,poll,epoll 的IO多路复用,在 coreclr源代码中我们都能找到它们的影子。

  1. select & poll

在平台适配层的 pal.cpp 文件中,有这样的一句话。


#if HAVE_POLL
#include <poll.h>
#else
#include "pal/fakepoll.h"
#endif  // HAVE_POLL

简而言之就是在不支持 poll 的linux版本中使用 select(fakepoll) 模拟,参考代码如下:

  1. 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);

  1. 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;

  1. 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.  */

  1. 将 STDIN_FILENO=0 塞入到可读句柄监控 (readfds) 中。
  2. 数据进来之后 select 被唤醒,执行后续逻辑。
  3. 通过 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的底层借助了。

  1. 红黑树:对句柄进行管理,复杂度为 O(logN)。
  2. 就绪队列:一旦句柄变得可读或可写,内核会直接将句柄送到就绪队列。

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利用率
内存开销

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2402483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从零开始的嵌入式学习day33

网络编程及相关概念 UDP网络通信程序 UDP网络通信操作 一、网络编程及相关概念 1. 网络编程概念&#xff1a; 指通过计算机网络实现程序间通信的技术&#xff0c;涉及协议、套接字、数据传输等核心概念。常见的应用场景包括客户端-服务器模型、分布式系统、实时通信等。…

黑马Java面试笔记之框架篇(Spring、SpringMvc、Springboot)

一. 单例bean Spring框架中的单例bean是线程安全的吗&#xff1f; Spring框架中的bean是单例的&#xff0c;可以在注解Scope()进行设置 singleton&#xff1a;bean在每一个Spring IOC容器中只有一个实例。prototype&#xff1a;一个bean的定义可以有多个实例 总结 二. AOP AOP称…

全球IP归属地查询接口如何用C#进行调用?

一、什么是全球IP归属地查询接口 在全球化互联网时代&#xff0c;IP地址作为网络世界的地理位置标识&#xff0c;扮演着至关重要的角色。全球IP归属地查询接口通过解析IP地址&#xff0c;提供包括国家、省、市、区县和运营商在内的详细信息。 二、应用场景 1. 访问识别 全球…

NumPy 比较、掩码与布尔逻辑

文章目录 比较、掩码与布尔逻辑示例&#xff1a;统计下雨天数作为通用函数&#xff08;Ufuncs&#xff09;的比较运算符使用布尔数组计数条目布尔运算符 布尔数组作为掩码使用关键字 and/or 与运算符 &/| 的区别 比较、掩码与布尔逻辑 本文介绍如何使用布尔掩码来检查和操…

力扣HOT100之二分查找:35. 搜索插入位置

这道题属于是二分查找的入门题了&#xff0c;我依稀记得一些二分查找的编码要点&#xff0c;但是最后还是写出了一个死循环&#xff0c;无语(ˉ▽ˉ&#xff1b;)…又回去看了下自己当时的博客和卡哥的视频&#xff0c;这才发现自己分情况只分了两种&#xff0c;最后导致死循环…

使用API有效率地管理Dynadot域名,查看域名市场中所售域名的详细信息

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…

IM即时通讯软件,构建企业局域网内安全协作

安全与权限&#xff1a;协同办公的企业级保障 在协同办公场景中&#xff0c;BeeWorks 将安全机制贯穿全流程。文件在局域网内传输与存储时均采用加密处理&#xff0c;企业网盘支持水印预览、离线文档权限回收等功能&#xff0c;防止敏感资料外泄&#xff1b;多人在线编辑文档时…

VueScan:全能扫描,高清输出

在数字化办公和图像处理的领域&#xff0c;扫描仪扮演着不可或缺的角色。无论是文档的数字化存档、照片的高清复制&#xff0c;还是创意项目的素材采集&#xff0c;一款性能卓越、操作便捷的扫描软件能大幅提升工作效率和成果质量。VueScan正是这样一款集多功能于一身的扫描仪软…

PyCharm项目和文件运行时使用conda环境的教程

打开【文件】—【新建项目】 按照下图配置环境 可以看到我这个项目里&#xff0c;报错“No module named modelscope” 点击终端&#xff0c;输入命令 #显示所有的conda环境 conda env list #选择需要激活的conda环境 conda activate XXX在终端中&#xff0c;执行pip install …

DeepSwiftSeek 开源软件 |用于 DeepSeek LLM 模型的 Swift 客户端 |轻量级和高效的 DeepSeek 核心功能通信

​一、软件介绍 文末提供程序和源码下载 DeepSeek Swift SDK 是一个轻量级且高效的基于 Swift 的客户端&#xff0c;用于与 DeepSeek API 进行交互。它支持聊天消息完成、流式处理、错误处理以及使用高级参数配置 DeepSeekLLM。 二、Features 特征 Supports chat completion …

Flask-Login使用示例

项目结构 首先创建以下文件结构&#xff1a; flask_login_use/ ├── app.py ├── models.py ├── requirements.txt └── templates/├── base.html├── index.html├── login.html├── register.html└── profile.html1. requirements.txt Flask2.3.3 Fl…

web第九次课后作业--SpringBoot基于mybatis实现对数据库的操作

前言 在前面我们学习MySQL数据库时&#xff0c;都是利用图形化客户端工具(如&#xff1a;idea、datagrip)&#xff0c;来操作数据库的。 在客户端工具中&#xff0c;编写增删改查的SQL语句&#xff0c;发给MySQL数据库管理系统&#xff0c;由数据库管理系统执行SQL语句并返回执…

wordpress免费主题网站

这是一款WordPress主题&#xff0c;由jianzhanpress开发&#xff0c;可以免费下载。专为中小微企业设计&#xff0c;提供专业的网站建设、网站运营维护、网站托管和网站优化等服务。主题设计简约、现代&#xff0c;适合多种行业需求。 主要特点&#xff1a; 多样化展示&#…

Go中的协程并发和并发panic处理

1 协程基础 1.1 协程定义&#xff08;Goroutine&#xff09; 概念&#xff1a;Go 语言特有的轻量级线程&#xff0c;由 Go 运行时&#xff08;runtime&#xff09;管理&#xff0c;相比系统线程&#xff08;Thread&#xff09;&#xff0c;创建和销毁成本极低&#xff0c;占用…

Qt Creator工具编译器配置

1、打开Qt Creator&#xff0c;工具-->选项 2、选择"编译器"&#xff0c;Manual配置编译器。 初始化填入“C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\cl.exe”&#xff0c;选择64位amd64。 ABI根据msvc版本进行选择msvc2015. 3、新建项目…

Spring框架学习day7--SpringWeb学习(概念与搭建配置)

SpringWeb1.SpringWeb特点2.SpringWeb运行流程3.SpringWeb组件4.搭建项目结构图&#xff1a;4.1导入jar包4.2在Web.xml配置**4.2.1配置统一拦截分发器 DispatcherServlet**4.2.2开启SpringWeb注解&#xff08;spring.xml&#xff09; 5.处理类的搭建6.SpringWeb请求流程(自己理…

打造高效多模态RAG系统:原理与评测方法详解

引言 随着信息检索与生成式AI的深度融合&#xff0c;检索增强生成&#xff08;RAG, Retrieval-Augmented Generation&#xff09; 已成为AI领域的重要技术方向。传统RAG系统主要依赖文本数据&#xff0c;但真实世界中的信息往往包含图像、表格等多模态内容。多模态RAG&#xf…

【C#】Quartz.NET怎么动态调用方法,并且根据指定时间周期执行,动态配置类何方法以及Cron表达式,有请DeepSeek

&#x1f339;欢迎来到《小5讲堂》&#x1f339; &#x1f339;这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。&#x1f339; &#x1f339;温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01;&#…

02 Deep learning神经网络的编程基础 逻辑回归--吴恩达

逻辑回归 逻辑回归是一种用于解决二分类任务&#xff08;如预测是否是猫咪等&#xff09;的统计学习方法。尽管名称中包含“回归”&#xff0c;但其本质是通过线性回归的变体输出概率值&#xff0c;并使用Sigmoid函数将线性结果映射到[0,1]区间。 以猫咪预测为例 假设单个样…

MySQL的并发事务问题及事务隔离级别

一、并发事务问题 1). 赃读&#xff1a;一个事务读到另外一个事务还没有提交的数据。 比如 B 读取到了 A 未提交的数据。 2). 不可重复读&#xff1a;一个事务先后读取同一条记录&#xff0c;但两次读取的数据不同&#xff0c;称之为不可重复读。 事务 A 两次读取同一条记录&…