【高并发服务器 03】——epoll模型

news/2024/5/9 6:20:38/文章来源:https://blog.csdn.net/weixin_55252589/article/details/136980111

epoll模型理论(从select到epoll)

select

select的算法时间复杂度略高,存在线性的性能下降问题(需要遍历访问文件描述符)。并且,受限于早期的内核资源的限制,select能够监视的文件描述符的数量不超过1024个。这个是他的缺陷

但是他的创新之处在于:他把多线程多进程的机制改成为一个线程就可以实现并发管理

  • 早期的时候:Apache(网页服务器,http服务器),用户如果想访问,Apache会开一个进程跟用户去沟通,并由这个进程给用户提供服务,用户下去之后,这个进程就被销毁了。

    这种一个用户一个进程的模式存在一个很大的问题,进程消耗资源比较多:PCB,进程空间,进程之间的交互也很难。虽然后来有了线程,成本代价远低于进程,但也需要一定的代价。

select支持1024个并发度,假如用select写一个Apache,就意味着,如果有1024个客户端接到Apache服务器,Apache完全可以用select监视他们,谁给我发个request请求,我就给他发一个response,如果他没有数据请求,我就等着直到他下线。


poll

通过查看poll的文档,我们可以得出两个结论:

  1. poll的文件描述符不再受到1024的限制(因为poll底层使用了链表这种数据结构,而select底层是用了类似数组这样的结构)
  2. poll引入了事件的概念,将文件描述符和感兴趣的事件比特掩码的形式)绑定到一起,把返回的事件放到revents中,因此poll没有对出事提交的“表单”进行任何修改,我至少没有必要在每一次循环的时候都像select那样进行初始化设置。
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

我们可以看到,select管理了三个集合,分别标记三个你所感兴趣的事件。每次都要一开始写好,并收回来查看这三张纸,哪个可以存钱,哪个可以取钱,哪个异常。而poll就不需要修改fd和events的信息了,返回消息都存放在revents里面。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);struct pollfd {int   fd;         /* file descriptor */short events;     /* requested events */short revents;    /* returned events */
};

epoll

linux2.6之后引入了epoll,并且只有linux平台有,epoll被设计成一个API(比函数高级一些,有一套组合的调用函数)。

  • epoll对于相应文件描述符可以采用**边缘触发(edge-triggered)水平触发(level-triggered)**两种模式,边缘触发表示,如果你在我发生的时候没有响应我,你就别再响应我了,或者只处理了我发过来的部分数据,后续数据也就不管了(更适合高并发的场景)。水平触发表示,如果事件发生了,我就必须响应你,如果在发生的那一刻我没来得及响应你,那么之后我也必须响应你(更适合安全场景)。

  • epoll中还使用了mmap技术,节省了内核态到用户态的拷贝

    注意:mmap只用在epoll实例里面:epoll_create创建并返回一个epoll实例,这个实例文件在自己的进程空间里有一片存储空间(里面存储了文件描述符集合),为了避免将实例中的数据,在每次传送给内核让内核去修改的时候,内核需要首先对数据进行拷贝,然后在这份拷贝的基础上对数据进行修改,最终将这份拷贝传送给用户态。mmap节省的是这个过程中的拷贝。

  • 而且epoll也没有文件描述符数量上的限制。也不存在性能的线性降低的问题(用户可以直接获取到就绪的events而不需要挨个问文件描述符)

epoll_ctl() 将感兴趣的文件描述符注册进实例空间。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);typedef union epoll_data {void        *ptr;int          fd;uint32_t     u32;uint64_t     u64;
} epoll_data_t;struct epoll_event {uint32_t     events;      /* Epoll events */epoll_data_t data;        /* User data variable */
};

event里的data采取了共用体union的形式,丰富了传入参数的多样性选择

epoll_wait() 等待IO事件,如果没有事件就绪就阻塞当前线程,直到时间超时(timeout=-1会无限期等下去)

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

epoll_wait功能就是在epfd这个epoll实例当中监听事件的发生,并将这些就绪的事件按顺序存储在events指针所指的连续数据当中,最多maxevents个,返回int发生就绪事件的个数。


总结一下select和epoll的对比,三者主要在以下三个方面差异:

  1. 监视的文件描述符数量:有没有限制
  2. 时间复杂度:是否存在性能的线性降低
  3. 内核空间:是否需要频繁地拷贝,mmap的引入

epoll小作业:TCP 100并发服务器的实现

1.task1.c

/*************************************************************************> File Name: 1.task1.c> Author: jby> Mail: > Created Time: Sun 24 Mar 2024 09:02:20 AM CST************************************************************************/#include "head.h"
#define MAXUSER 2000   // 2000并发
#define MAXEVENTS 10   // epoll支持的并发事件数
#define INS 4 		   // 线程池中线程的数量
#define QUEUESIZE 100  // 任务队列长度int clients[MAXUSER];  // 全局的用户fd管理区
char *data[MAXUSER];   // 全局的数据存放区
int epollfd, total;    // 全局的epollfd; total:并发数
pthread_mutex_t mutex[MAXUSER]; // 每个客户端配一个互斥锁,为什么不是给线程配而是给用户配,因为用户的数据存放的区域已经按照不同的用户分隔开了,对于不同线程取到同一用户的不同数据,需要加锁处理。void logout(int sig) {DBG("total = %d\n", total);exit(1);
}
void freeAll() {for (int i = 0; i < MAXUSER; i++) {free(data[i]);}return ;
}
int main (int argc, char **argv) {if (argc != 2) {fprintf(stderr, "Usage : %s port.\n", argv[0]); // 因为写的是服务端,所以要设置端口exit(1);}int server_listen, port, sockfd;port = atoi(argv[1]); // 将字符串串转换为整数if ((server_listen = socket_create(port)) < 0) {perror("socket_create");exit(1);}DBG(YELLOW"<Init> : server_listen %d start on port %d .\n"NONE, server_listen, port); // 打印在哪个端口监听,监听套接字// 下面考虑用线程池// 开启线程池之前需要首先创建一个任务队列struct task_queue *taskQueue = (struct task_queue *)calloc(1, sizeof(struct task_queue)); // 用指针的形式task_queue_init(taskQueue, QUEUESIZE); // 任务队列的初始化DBG(YELLOW"<Init> : task_queue init.\n"NONE);// 下面创建几个线程// pthread_t tid[INS]; pthread_t *tid = (pthread_t *)calloc(INS, sizeof(pthread_t)); // 等价的写法,但是空间申请在堆区// 启动每一个线程for (int i = 0; i < INS; i++) {pthread_create(&tid[i], NULL, thread_work, (void *)taskQueue); // NULL:这个参数是指向线程属性对象的指针。在这个例子中,通过传递 NULL,我们指定使用默认的线程属性。}DBG(YELLOW"<Init> : work threads create.\n");// 锁需要初始化才能用for (int i = 0; i < MAXUSER; i++) {pthread_mutex_init(&mutex[i], NULL);}DBG(YELLOW"<Init> : pthread mutex init.\n"NONE);// 初始化全局数据区for (int i = 0; i < 2000; i++) {data[i] = (char *)calloc(4096, sizeof(char));}// 创建epoll实例if ((epollfd = epoll_create(1)) < 0) { // epoll_create(size):size只需要大于0即可,作用可以忽略perror("epoll_create");exit(1);}// 注册epoll事件struct epoll_event ev, events[MAXEVENTS];// 为了让server_listen能够监听客户端,第一个事件便是把server_listen的fd注册进epoll实例ev.data.fd = server_listen;ev.events = EPOLLIN;	// EPOLLIN:这是一个宏,表示对应的文件描述符可读。具体来说,它表示文件描述符上有新的输入数据可读,或者监听的 socket 上有新的连接尝试,或者是一个管道的写端已经关闭,使得读操作不会再被阻塞。if (epoll_ctl(epollfd, EPOLL_CTL_ADD, server_listen, &ev) < 0) {perror("epoll_ctl");exit(1);}DBG(YELLOW"<Init> : Epoll instance created and add server_listen.\n"NONE);signal(SIGINT, logout); // SIGINT是一个宏定义,代表“中断信号”。这是一个特定的信号,通常由用户按下Ctrl+C产生,用于请求中断一个程序。当SIGINT信号被触发时,系统将调用这个logout函数。// 等待客户端链接到达。for (;;) {int nfds = epoll_wait(epollfd, events, MAXEVENTS, -1); // -1: 这个参数指定了 epoll_wait 函数的超时时间,以毫秒为单位。在这个例子中,值为 -1 表示 epoll_wait 函数将无限期地等待,直到至少有一个监视的文件描述符上发生了事件。如果这个参数设置为非负值,epoll_wait 将在指定的时间后返回,即使没有事件发生。如果设置为 0,epoll_wait 将立即返回,这种情况通常用于非阻塞轮询。// 返回响应事件的fd个数,并且将事件挨个填写到events数组当中if (nfds <= 0) {perror("epoll_wait");exit(1);}total += nfds;for (int i = 0; i < nfds; i++) {int fd = events[i].data.fd; // 提取出事件的fdif (fd == server_listen && (events[i].events & EPOLLIN)) {// 这意味着有新的客户端的TCP请求到来,客户端需要先accept接收,用一个新的sockfd代表客户端if ((sockfd = accept(server_listen, NULL, NULL)) < 0) { // NULL, NULL: 这两个 NULL 参数分别对应于 accept 函数的第二个和第三个参数。第二个参数(这里是第一个 NULL)如果不是 NULL,它应该是指向 sockaddr 结构的指针,该结构用于接收连接方的协议地址(例如,IP 地址和端口号)。第三个参数(这里是第二个 NULL)如果不是 NULL,它应该是指向 socklen_t 类型的变量的指针,该变量在输入时表示地址结构的长度,在输出时表示实际存储在地址结构中的字节数。在这个例子中,因为这两个参数都是 NULL,所以我们不关心连接方的地址信息。perror("accept");exit(1);}// 再把客户端fd注册进epoll实例ev.data.fd = sockfd;ev.events = EPOLLIN | EPOLLET; // 设为边缘触发模式,这样就只会断开一次clients[sockfd] = sockfd;make_nonblock(sockfd);DBG(CYAN"make_nonblock fd = %d\n"NONE, sockfd);if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) < 0) {perror("epoll_ctl");exit(1);}} else {if (events[i].events & EPOLLIN) {// 如果是客户端发来数据,那么添加进任务队列中task_queue_push(taskQueue, (void *)&clients[fd]); // 现在是把fd传进任务队列,而不是把数据传进任务队列}if (events[i].events & EPOLLHUP) { // 异常epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);close(fd);}}}}freeAll();free(taskQueue);
}

用telnet 测试本地即可。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索智慧农业精准除草,基于高精度YOLOv8全系列参数【n/s/m/l/x】模型开发构建农田作物场景下杂草作物分割检测识别分析系统

智慧农业是未来的一个新兴赛道&#xff0c;随着科技的普及与落地应用&#xff0c;会有更加广阔的发展空间&#xff0c;关于农田作物场景下的项目开发实践&#xff0c;在我们前面的博文中也有很堵相关的实践&#xff0c;单大都是偏向于目标检测方向的&#xff0c;感兴趣可以自行…

鸿蒙网络开发学习:【ylong_http】

简介 ylong_http 构建了完整的 HTTP 能力&#xff0c;支持用户使用 HTTP 能力完成通信场景的需求。 ylong_http 使用 Rust 编写&#xff0c;为 OpenHarmony 的 Rust 能力构筑提供支持。 ylong_http 在 OpenHarmony 中的位置 ylong_http 向 OpenHarmony 系统服务层中的网络协…

【C语言】指针基础知识(二)

一&#xff0c;指针变量类型的意义 1&#xff0c;指针的类型决定了&#xff0c;对指针解引⽤的时候有多⼤的权限&#xff08;⼀次能操作⼏个字节&#xff09;。 例如&#xff1a;char* 的指针解引⽤访问⼀个字节&#xff0c;int* 的指针解引⽤访问四个字节&#xff0c;short*…

关于 Flutter 项目中已为整个 APP 配置了主题颜色但是在 AppBar 等某些组件中主题颜色不生效的问题

这里需要先说明的&#xff0c;从 Flutter 2.5 开始&#xff0c;Flutter 团队开始慢慢移除ThemeData 中 primaryColor 属性对所有组件的影响&#xff0c;取而代之的是基于 ColorScheme 的 Color。因此&#xff0c;在 Flutter 2.5 之后为整个 APP 配置主题颜色&#xff0c;我们需…

vscode c++环境配置

1.基础软件安装 安装Visual Studio Code. 安装C拓展。点击在vscode界面最左侧的Extensions图标&#xff08;打开快捷键&#xff1a;ctrlshiftX&#xff09;&#xff0c;搜索“C/C”&#xff0c;点击进行安装。 确保已安装gcc. 一般ubuntu系统会预装gcc.在终端窗口中输入如下…

数据处理库Pandas的数据结构Series

Series是一种一维数据结构&#xff0c;每个元素都带有一个索引&#xff0c;与一维数组的含义相似&#xff0c;其中索引可以为数字或字符串&#xff0c;如图3-1所示。 Series 对象包含两个主要的属性&#xff1a;index 和 values&#xff0c;分别为上例中的左右两列。因为传给构…

GEE:将分类特征和标签提取到样本点,并以(csv/shp格式)下载到本地

作者:CSDN @ _养乐多_ 本文将介绍在Google Earth Engine(GEE)平台上,下载用于机器学习分类或者回归的样本点数据,样本点数据携带了分类特征和标签信息,可以以csv格式或者SHP格式。 结果如下图所示, 文章目录 一、核心函数1.1 采样1.2 下载函数二、代码链接三、完整代码…

大数据Spark--入门

文章目录 Spark 概述Spark 是什么Spark and HadoopSpark and HadoopSpark 核心模块 Spark 简单上手创建Maven项目增加 Scala 插件增加依赖关系WordCount异常处理 Spark 概述 Spark 所需资料 链接&#xff1a;https://pan.baidu.com/s/12iaW68vriL6i-xI1kmr0_g?pwdm4zc 提取码…

C语言指针详解(上)

一.什么是指针 指针是一种类型&#xff0c;用来存储变量的地址的类型 有哪些类型呢 字符指针&#xff1a;char* 整型指针&#xff1a;int* 浮点型指针&#xff1a;float* 双精度浮点型指针&#xff1a;double* 空指针&#xff1a;void* &#xff08;每一个类型的指针&a…

Python学习之-正则表达式

目录 前言&#xff1a;1.re.serach1.1例子&#xff1a; 2.re.match2.1示例1&#xff1a;2.2 示例2&#xff1a; 3.re.findall3.1 示例 4.re.fullmatch4.1 示例1&#xff1a;4.2 示例2: 5.re.split5.1 示例1:5.2 示例2&#xff1a;5.3 示例3&#xff1a; 6.re.sub6.1 示例&#…

langchin-chatchat部分开发笔记(持续更新)

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径及一点个人思考大模型应用开发实用开源项目汇总大模型问答项目…

Python图像处理——计算机视觉中常用的图像预处理

概述 在计算机视觉项目中&#xff0c;使用样本时经常会遇到图像样本不统一的问题&#xff0c;比如图像质量&#xff0c;并非所有的图像都具有相同的质量水平。在开始训练模型或运行算法之前&#xff0c;通常需要对图像进行预处理&#xff0c;以确保获得最佳的结果。图像预处理…

巨控GRM560工业物联网的升级后的功能

巨控GRM560&#xff1a;工业自动化领域的革命者 标签:#工业自动化 #PLC #远程控制 #OPCUA #MQTT 随着工业4.0时代的到来&#xff0c;智能制造已经成为了发展的大势所趋。在这样的背景下&#xff0c;自动化控制系统的核心——可编程逻辑控制器&#xff08;PLC&#xff09;的作用…

pytorch如何向tensor结尾添加元素或维度--torch.cat()、torch.unsqueeze()的用法

目录 示例1 矢量后增加元素 示例2 tensor维度增加1 示例3 另一种替代unsqueeze的方法 示例1 矢量后增加元素 使用torch.cat()函数 ptorch.Tensor([1,5,0]) ptorch.cat((p, torch.Tensor([4])), 0) 结果&#xff1a; 这里&#xff0c;cat的第一个输入变量用()包绕&#xf…

Vue.js高效前端开发(增删查)

效果图 代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><div id"app"><span>ID</span><input type"text" name"…

javaSwing五子棋游戏

一、导言 五子棋&#xff0c;是一种源自中国古代的棋类游戏&#xff0c;也是一种非常古老和经典的对弈游戏。它简单易懂&#xff0c;规则清晰&#xff0c;深受广大玩家喜爱。本文将介绍如何利用Java Swing这个强大的GUI工具包&#xff0c;来实现一个简单的五子棋游戏。通过这个…

mysql基础2多表查询

多表查询 多表关系: 一对多 案例: 部门 与 员工的关系 关系: 一个部门对应多个员工&#xff0c;一个员工对应一个部门 实现: 在多的一方建立外键&#xff0c;指向一的一方的主键 多对多 案例: 学生 与 课程的关系 关系: 一个学生可以选修多门课程&#xff0c;一门课程也可以…

《论文阅读》PAGE:一个用于会话情绪原因蕴含基于位置感知的图模型 ICASSP 2023

《论文阅读》PAGE&#xff1a;一个用于会话情绪原因蕴含基于位置感知的图模型 ICASSP 2023 前言 简介任务定义模型构架Utterances Encoding with EmotionPosition-aware GraphCausal Classifier实验结果 前言 亲身阅读感受分享&#xff0c;细节画图解释&#xff0c;再也不用担…

鸿蒙开发之ArkUI组件常用组件图片和文本

ArkUI即方舟开发框架是HarmonyOS应用的UI开发提供了完整的基础设施&#xff0c;包括简洁的UI语法、丰富的UI功能&#xff08;组件、布局、动画以及交互事件&#xff09;&#xff0c;以及实时界面预览工具等&#xff0c;可以支持开发者进行可视化界面开发。 开发文档地址 &…

国产数据库中统计信息自动更新机制

数据库中统计信息描述的数据库中表和索引的大小数以及数据分布状况&#xff0c;统计信息的准确性对优化器选择执行计划时具有重要的参考意义。本文简要整理了下传统数据库和国产数据库中统计信息的自动更新机制&#xff0c;以加深了解。 1、数据库统计信息介绍 优化器是数据库…