怎么做到高性能网络IO?
为什么要做高性能网络IO。主要是解决c10,c10M问题
最开始的时候我们走的内核协议栈,走内核协议栈其实性能比较低,因为我们之前介绍的时候需要拷贝两次
但是我们采用用户态协议栈可以少拷贝一次,可以大大提高效率,
步骤:
1)客户端请求数据,先经过网卡,服务器需要从网卡copy数据到内核协议栈(tcp/bsd)。
2)再从内核协议栈copy数据到应用程序。
由此可见,客户端与应用程序之间的数据交互,多了两次数据拷贝的操作,在大量数据并发的情况下,必将会严重影响性能。
优化思路:可以跳过内核协议栈,去除拷贝操作,数据直接从网卡到应用程序,这种方式称为零拷贝。
但是我们这个在做完用户态协议栈在配合reactor的时候,会出现一个问题,是什么问题呢?
问题是:我们的epoll并不会通知这个事件
那么我们是怎么看待这个问题的呢,我们如果不了解epoll的原理和底层的话,我们一下也不知道为什么epoll不会通知,其实是因为epoll的通知是由内核通知的,但是我们旁路之后,不走内核协议栈,那么内核协议栈就不会通知数据了
图例:(我们是在内核里创建了红黑树和fd的一些结构体信息,然后提供系统调用给用户)
协议栈解析出有数据来,通知到epoll中。应用程序操作epoll
所以我们在走用户态协议栈的时候,就不能用系统自带的epoll了,需要自己再用户态实现一个
epoll,进行管理
我们怎么设计epoll呢,我们采用红黑树结构是最好的,排除掉哈希表,优先队列,链表
就是红黑树最好了,为什么,因为红黑树这个数据结构更适合增删改查,效率也高,并且有
带有二叉搜索树的性质,所以很好用
需要查找性能高的数据结构,可选的数据结构有
hash
:fd 数量不确定,创建 hash 消耗大量的内存。若 fd 数量较少时,内存浪费多,性能低b/b+
树:查找性能低于红黑树,降低树高,用于磁盘 iorbtree
:查找性能高,效率稳定,这里选用红黑树
还有一点就是epoll 监听的是系统 fd。而在自定义用户态协议栈的过程中,我们定义的 fd 只是个 int 值,并不指向内核打开文件表中对应的 i-node 结点
epoll 通过 fd 检测协议栈中的 tcb 有无事件发生,并对这些 fd 进行管理。
自定义epoll 的主要结构体有
-
epitem
:存储每个 io 对应的事件,每个注册到 epoll 池的 fd 对应1个 epitem
// 自定义的 epitem
struct epitem {RB_ENTRY(epitem) rbn; // 红黑树的结点LIST_ENTRY(epitem) rdlink; // 就绪队列,双向链表结点int rdy; // 是否在就绪队列中int sockfd; // 事件对应的sockfdstruct epoll_event event; // 注册事件的类型
};
eventpoll
:用于管理1个 epoll 对象
// 自定义的 eventpoll
struct eventpoll {int fd; // epfdep_rb_tree rbr; // 红黑树的根结点int rbcnt; LIST_HEAD( ,epitem) rdlist; // 就绪队列头结点int rdnum; int waiting; // epoll_wait判断是否正在等待pthread_mutex_t mtx; //rbtree updatepthread_spinlock_t lock; //rdlist updatepthread_cond_t cond; //block for event,用于epoll_wait的超时等待pthread_mutex_t cdmtx; //mutex for cond
};
红黑树和双向链表共用结点 epitem。
双向链表采用的是就绪队列,在处理事件的时候,可以按先来先服务策略进行处理时间
2、epoll 锁机制
考虑两个公共资源:红黑树和就绪队列。
- 红黑树:mutex,互斥锁
- 就绪队列:spinlock,采用自旋锁,避免 SMP 体系下,多核竞争。
我们的红黑树的删除和修改和插入都是采用互斥锁的,因为不用锁的的话会发生线程安全问题,比如我们将epoll交给多个线程管理,那么当事件就绪的时候就会有惊群效应,如果此时
不加锁的话,那么多个线程会同时去处理这个事件,那么就会出现线程安全问题
3、epoll 用户态接口
epoll 为用户态提供的接口有:epoll_create
, epoll_ctl
, eoll_wait
3.1、epoll_create 的实现
功能: 创建 eventpoll 结构体
int epoll_create(int size) {if (size <= 0) return -1;// 从位图中获取新的fd,fd从3开始依次递增 int epfd =get_fd_frombitmap();struct eventpoll *ep = (struct eventpoll*)rte_calloc("eventpoll",1, sizeof(struct eventpoll), 0);if (!ep) {// 创建失败,将fd从位图中删除set_fd_frombitmap(epfd);return -1;}// 初始化红黑树和就绪队列ep->rbcnt = 0;RB_INIT(&ep->rbr);LIST_INIT(&ep->rdlist);if (pthread_mutex_init(&ep->mtx, NULL)) {rte_free(ep);set_fd_frombitmap(epfd);return -2;}if (pthread_mutex_init(&ep->cdmtx, NULL)) {pthread_mutex_destroy(&ep->mtx);rte_free(ep);set_fd_frombitmap(epfd);return -2;}if (pthread_cond_init(&ep->cond, NULL)) {pthread_mutex_destroy(&ep->cdmtx);pthread_mutex_destroy(&ep->mtx);rte_free(ep);set_fd_frombitmap(epfd);return -2;}if (pthread_spin_init(&ep->lock, PTHREAD_PROCESS_SHARED)) {pthread_cond_destroy(&ep->cond);pthread_mutex_destroy(&ep->cdmtx);pthread_mutex_destroy(&ep->mtx);rte_free(ep);set_fd_frombitmap(epfd);return -2;}return epfd;
}
3.2、epoll_ctl 的实现
功能:对红黑树进行增添,修改、删除。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) {// 通过fd查找到协议栈中对应的tcb连接,返回 eventpoll 对象,fd -> hoststruct eventpoll *ep = (struct eventpoll *)get_hostinfo_fromfd(epfd);// 若ep对象为空,或没有要设置的事件(del除外)if (!ep || (!event && op != EPOLL_CTL_DEL)) {errno = -EINVAL;return -1;}///1、ADD 操作if (op == EPOLL_CTL_ADD) {pthread_mutex_lock(&ep->mtx);struct epitem tmp;tmp.sockfd = fd;// 在红黑树查找该结点 struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);// 若红黑树已经存在该结点,返回if (epi) {pthread_mutex_unlock(&ep->mtx);return -1;}// 不存在,则创建 epitem 结点,并为其添加sockfd和事件epi = (struct epitem*)rte_calloc("epitem",1, sizeof(struct epitem), 0);if (!epi) {pthread_mutex_unlock(&ep->mtx);errno = -ENOMEM;return -1;} epi->sockfd = fd;memcpy(&epi->event, event, sizeof(struct epoll_event));// 插入到红黑树中epi = RB_INSERT(_epoll_rb_socket, &ep->rbr, epi);assert(epi == NULL);// 红黑树结点数量增加ep->rbcnt ++;pthread_mutex_unlock(&ep->mtx);} // 2、DEL 操作else if (op == EPOLL_CTL_DEL) {pthread_mutex_lock(&ep->mtx);struct epitem tmp;tmp.sockfd = fd;struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);// 若红黑树中不存在该结点,直接返回if (!epi) {pthread_mutex_unlock(&ep->mtx);return -1;}// 存在该结点,则从红黑树中删除epi = RB_REMOVE(_epoll_rb_socket, &ep->rbr, epi);if (!epi) {pthread_mutex_unlock(&ep->mtx);return -1;}// 红黑树结点数量减少ep->rbcnt --;// 释放结点空间rte_free(epi);pthread_mutex_unlock(&ep->mtx);} // 3、MOD 操作else if (op == EPOLL_CTL_MOD) {struct epitem tmp;tmp.sockfd = fd;struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);// 该结点存在,则修改if (epi) {epi->event.events = event->events;epi->event.events |= EPOLLERR | EPOLLHUP;} // 不存在,返回-1else {errno = -ENOENT;return -1;}} // 4、非法操作else {assert(0);}return 0;
}
3.3、epoll_wait 的实现
功能:等待 fd 就绪,监控就绪队列,若有数据,从内核拷贝数据到用户空间;若没有数据,阻塞。
等待的实现方法
等待规定的时间,条件变量 + pthread_cond_timedwait
一直等待(阻塞),条件变量 + pthread_cond_wait
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) {// 通过fd查找到协议栈中对应的tcb连接,返回 eventpoll 对象,fd -> hoststruct eventpoll *ep = (struct eventpoll *)get_hostinfo_fromfd(epfd);// 若ep对象为空,或没有要设置的事件(del除外)if (!ep || (!event && op != EPOLL_CTL_DEL)) {errno = -EINVAL;return -1;}///1、ADD 操作if (op == EPOLL_CTL_ADD) {pthread_mutex_lock(&ep->mtx);struct epitem tmp;tmp.sockfd = fd;// 在红黑树查找该结点 struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);// 若红黑树已经存在该结点,返回if (epi) {pthread_mutex_unlock(&ep->mtx);return -1;}// 不存在,则创建 epitem 结点,并为其添加sockfd和事件epi = (struct epitem*)rte_calloc("epitem",1, sizeof(struct epitem), 0);if (!epi) {pthread_mutex_unlock(&ep->mtx);errno = -ENOMEM;return -1;} epi->sockfd = fd;memcpy(&epi->event, event, sizeof(struct epoll_event));// 插入到红黑树中epi = RB_INSERT(_epoll_rb_socket, &ep->rbr, epi);assert(epi == NULL);// 红黑树结点数量增加ep->rbcnt ++;pthread_mutex_unlock(&ep->mtx);} // 2、DEL 操作else if (op == EPOLL_CTL_DEL) {pthread_mutex_lock(&ep->mtx);struct epitem tmp;tmp.sockfd = fd;struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);// 若红黑树中不存在该结点,直接返回if (!epi) {pthread_mutex_unlock(&ep->mtx);return -1;}// 存在该结点,则从红黑树中删除epi = RB_REMOVE(_epoll_rb_socket, &ep->rbr, epi);if (!epi) {pthread_mutex_unlock(&ep->mtx);return -1;}// 红黑树结点数量减少ep->rbcnt --;// 释放结点空间rte_free(epi);pthread_mutex_unlock(&ep->mtx);} // 3、MOD 操作else if (op == EPOLL_CTL_MOD) {struct epitem tmp;tmp.sockfd = fd;struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);// 该结点存在,则修改if (epi) {epi->event.events = event->events;epi->event.events |= EPOLLERR | EPOLLHUP;} // 不存在,返回-1else {errno = -ENOENT;return -1;}} // 4、非法操作else {assert(0);}return 0;
}
4、epoll 回调
4.1、epoll 回调函数的实现
当内核 io 准备就绪的时候,执行 epoll 回调函数,将 epitem 添加到 rdlist 中,唤醒 epoll_wait。当 epoll_wait 被激活重新运行的时候,将 rdlist 的 epitem 逐一拷贝到 events 中,同时删除 rdlist 中对应的结点。换句话说, epoll_callback 是生产者,放入结点,唤醒 epoll_wait;epoll_wait 是消费者,消费结点。
// 从协议栈回调到epoll,把fd和对应的事件拷贝到应用程序
static int nepoll_event_callback(struct eventpoll *ep, int sockid, uint32_t event) {struct epitem tmp;tmp.sockfd = sockid;// 在红黑树中查找 epitemstruct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);if (!epi) {return -1;}// 已经在就绪队列中,只添加事件if (epi->rdy) {epi->event.events |= event;return 1;} // 不在就绪队列,则将结点加入到就绪队列pthread_spin_lock(&ep->lock);epi->rdy = 1;LIST_INSERT_HEAD(&ep->rdlist, epi, rdlink);ep->rdnum ++;pthread_spin_unlock(&ep->lock);pthread_mutex_lock(&ep->cdmtx);// 就绪队列中增加结点,唤醒epoll_waitpthread_cond_signal(&ep->cond);pthread_mutex_unlock(&ep->cdmtx);return 0;}
4.2、epoll 回调的时机
触发 epoll 回调4个时机,需要在这些地方添加 epoll 回调函数,使得 epoll 可以正常接收数据。
三次握手中,在 syn-rcvd 状态,对端返回 ack 后,tcb 结点放入到全连接队列,将对应的 sockfd 的置为 EPOLLIN 状态,等待 accept 取出,触发 epoll 回调。
if (stream->status == TCP_SYN_RCVD) {// 进入到 ESTABLISHED 状态stream->status = TCP_STATUS_ESTABLISHED;// 设置 epoll 回调函数,等待 accept
}
在 established 状态,收到数据后,将 sockfd 置为 EPOLLIN 状态,等待读取数据,触发epoll 回调
if (tcphdr->tcp_flags & TCP_PSH_FLAG) {// 建立连接后,push 接收数据,设置 epoll 回调函数
}
在 established 状态,收到 fin 时,进入到 close_wait 状态。将 sockfd 的 event 置为 EPOLLIN,读取断开信息,触发 epoll 回调
if (tcphdr->tcp_flags & TCP_FIN_FLAG) {// 收到 fin,进入到 CLOSE_WAIT 状态stream->status = TCP_STATUS_CLOSE_WAIT; // 设置 epoll 回调函数,读取断开信息
}
-
检测 socket 的 send 状态,如果对端 cwnd>0, 可以发送数据,将 sockfd 置为 EPOLLOUT,等待发送数据
5、epoll 事件通知机制
水平触发(LT),有事件,则一直触发;边缘触发(ET),只触发一次,关注的是 io 状态的变化。
实现的关键是内核 io 就绪时,epoll 回调函数的执行次数。
LT,检测 recvbuffer 有数据则调用 epoll 回调函数
ET,从协议栈中检测到recvbuffer中接收数据就调用 epoll 回调函数
我们后面还可以用io_uring来处理,先不介绍了
相关文章:

怎么做到高性能网络IO?
为什么要做高性能网络IO。主要是解决c10,c10M问题 最开始的时候我们走的内核协议栈,走内核协议栈其实性能比较低,因为我们之前介绍的时候需要拷贝两次 但是我们采用用户态协议栈可以少拷贝一次,可以大大提高效率, 步骤…...
设计模式-创建型
文章目录 设计模式-创建型工厂模式简单工厂工厂方法抽象工厂 建造者模式单例模式原型模式 设计模式-创建型 本章主要介绍有关对象创建的几种设计模式。 工厂模式 工厂模式:封装了对象的创建,使得获得对象更加符合实际逻辑 简单工厂 将所有对象的生产…...

Word通过Adobe打印PDF时总是报错,打开记事本
Word文档打印,选择Adobe作为打印机,打印过程中总是报错,不断打开记事本,提示打印出错,错误信息如下: %%[ ProductName: Distiller ]%% %%[Page: 1]%% %%[Page: 2]%% %%[ Error: invalidfont; OffendingCom…...

第2关:还原键盘输入(list)
题目: 知识点: 列表list相较于数组: 优势:可在任意指定位置插入或者删除元素而不影响列表其他地方 。 劣势:无法直接进行下标索引,需要迭代器it逐个遍历。 代码: #include <iostream>…...

数据结构 | 栈的实现
数据结构 | 栈的实现 文章目录 数据结构 | 栈的实现栈的概念及结构栈的实现 Stack.h初始化栈入栈出栈获取栈顶元素获取栈中有效元素个数检测栈是否为空销毁栈 Stack.c 栈的概念及结构 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。…...

python异常、模块与包
1.异常 异常:当检测到一个错误时,Python解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的“异常”,也就是我们常说的BUG。 1.1捕获异常 基本语法: try:可能发生错误代码 except:如果出现…...
虚拟内存和物理内存
虚拟内存的概念 虚拟内存是计算机系统内存管理的一种技术,它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储…...
FCA例题
Part.1:判断题 第1题 智能运维-负载管理中,实时负载通过使用图表直观的展示当前系统的最多最近半小时内存利用率和CPU利用率(正确) 第2题 服务器安装插件支持热部署,安装、删除、更新、禁用、启用不需要重启(正确) 第3题 次级管理员可新建…...
mysql使用GROUP BY归组后把所有记录id汇总到一个字段中
可以使用MySQL的GROUP_CONCAT函数来实现将归组后的记录的ID汇总到一个字段中。假设有一个名为table1的表,其中包含id和name两个字段,可以使用以下查询: SELECT name, GROUP_CONCAT(id) AS ids FROM table1 GROUP BY name;这将返回一个结果集…...
Vue3 使用Element Plus表格单选带checkbox
官方地址:添加链接描述 官方给出的多选带checkbox,单选直接选中当前行高亮,有时候不想要单行高亮,想要带checkbox的单选,需要对多选进行改造 官方给的多选例子: <template><el-tableref"mult…...
IOC - 自定义IOC容器
1、定义接口与实现类 // Service接口 public interface Service {void execute(); } // Service的实现类 public class MyService implements Service {Overridepublic void execute() {System.out.println("MyService 执行了.");} }2、自定义ioc容器以绑定接口与实…...

力扣第647题 回文子串 c++ 动态规划 双指针 附Java代码 注释解释版
题目 647. 回文子串 中等 相关标签 字符串 动态规划 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串…...

【Go入门】struct类型
【Go入门】struct类型 struct Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器。例如,我们可以创建一个自定义类型person代表一个人的实体。这个实体拥有属性:姓名和年龄。这样…...

怎么改变容易紧张的性格?
容易紧张的性格是比较通俗的说法,在艾森克人格测试中,容易紧张的性格就属于神经症人格,神经质不是神-经-病,而是一种人格特征,这种特征包括:敏感,情绪不稳定,易焦虑和紧张。有兴趣的…...

合作共赢 共克时艰
采访人:最近财政部11月6日通报隐性债务问责典型案例,这中间涉及湖北多所重要地市,形成新增隐性债务200多亿,您怎么看这件事? 辜渝傧:是的,无论是数字还是涉及的范围都可以明显感觉到“防范…...
VCSA7许可证过期问题
公司两台ESXI7虚拟化系统,使用VCSA7进行日常管理,在使用过程中一直清单中包含过期或即将过期的许可证。 查看许可证清单中,已经添加了正式授权的许可证,且已经分配给了ESXI主机,但是任然有到期提示。 最后查看试用许可…...

解决win11更新后,文件夹打不开的bug
更新win11系统了,给我更了个bug,找了好多解决方案,发现下面这个可以解决问题。 第一步 找到注册表 第二步 备份注册表 为了防止意外情况,备份注册表。如有意外问题,可以导入导出的注册表进行恢复。 第三步 删除指定…...

修复了数个Bug!
v2.0.1版本已经在 github release 了,欢迎大家体验使用,开源版是永久免费的。 ## 新增与优化的功能 新增(测试报告): 测试报告根据测试执行详情,进行查看 新增(用户设置): 用户权限为普通用户和管理员,普通用户根据设置的默认产品…...

设计模式之--原型模式(深浅拷贝)
原型模式 缘起 某天,小明的Leader找到小明:“小明啊,如果有个发简历的需求,就是有个简历的模板,然后打印很多份,要去一份一份展示出来,用编程怎么实现呢?” 小明一听,脑袋里就有了…...

Linux服务器从零开始训练 RT-DETR 改进项目 (Ultralytics) 教程,改进RTDETR算法(包括使用训练、验证、推理教程)
手把手从零开始训练 RT-DETR 改进项目 (Ultralytics版本) 教程,改进RTDETR算法 本文以Linux服务器为例:从零开始使用Linux训练 RT-DETR 算法项目 《芒果剑指 RT-DETR 目标检测算法 改进》 适用于芒果专栏改进RT-DETR算法 文章目录 百度 RT-DETR 算法介绍改进网络代码汇总第…...

Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...

计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...

C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)
引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...