百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

Linux并发服务器模型五 -- epoll linux服务器实现

liebian365 2024-10-27 13:12 20 浏览 0 评论

多进程和多线程模型在实现中相对简单, 但其开销和CPU高度比较大, 一般不用多线程和多进程来实现服务器多路模型.

select由于其跨平台, 但其最高上限默认为1024, 修改突破1024的话需要重新编译linux内核, poll虽然解决了select1024的限制, 但由于poll本质实现上也是轮询机制, 所以对于客户端的增加也会使效率降低.

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率

  • 分析epoll API
//生成一个专用的文件描述符, size写多少就是多少, 但是如果运行过程中添加的越来越多, 后面的其实还会监听
 int epoll_create(int size); 
 //用于控制某个epoll文件描述符事件,可以注册、修改、删除
 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
 //epfd: 生成的专用描述符, 相当于一个树(红黑树)的根节点, 如果再有其他的添加进来, 则系统将其会挂载到其节点下面
 //op: EPOLL_CTL_ADD -- 注册, EPOLL_CTL_MOD -- 修改, EPOLL_CTL_DEL -- 删除
 //fd: 关联的文件描述符
//event: struct epoll_event 结构体类型的指针, 告诉内核要监听什么事件
//其中event结构体分析如下
struct epoll_event {
 uint32_t events; //结构体
 epoll_data_t data; //联合体
 }
/*
 events:
 - EPOLLIN - 读
 - EPOLLOUT - 写
 - EPOLLERR - 异常
 */
/*
typedef union epoll_data { 
 void *ptr;
 int fd; //常用
 uint32_t u32;
 uint64_t u64;
} epoll_data_t;
 */
/*等待IO事件发生 - 可以设置阻塞的函数, 对应select和poll函数
该函数能够告诉我们是哪个文件描述符发生变化, 并放到下面的数组中
因为传进去的时候结构体中包含结构体中含有fd和event*/
int epoll_wait(int epfd,
 struct epoll_event* events, //数组
 int maxevents,
 int timeout);
/*参数:
 epfd: 要检测的句柄
 events:用于回传待处理事件的数组
 maxevents:告诉内核这个events的大小 
 timeout:为超时时间
-1: 永久阻塞; 0: 立即返回; >0: 阻塞多久*/

server

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <ctype.h>
#define MAXLINE 8192
#define SERV_PORT 8000
#define OPEN_MAX 1000
int main(int argc, char *argv[]) {
 // 记录是第几个连接上来的客户端
 int num = 0;
 
 // 创建监听的套接字
 int listenfd = socket(AF_INET, SOCK_STREAM, 0);
 
 // 端口复用
 int opt = 1;
 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
 
 struct sockaddr_in servaddr;
 bzero(&servaddr, sizeof(servaddr));
 servaddr.sin_family = AF_INET;
 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 servaddr.sin_port = htons(SERV_PORT);
 //绑定
 bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
 
 listen(listenfd, 20);
 
 //创建epoll模型, efd指向红黑树根节点
 int efd = epoll_create(OPEN_MAX);
 if(efd == -1) {
 perror("epoll_create error");
 exit(1);
 }
 
 // tep: epoll_ctl参数
 struct epoll_event tep;
 //指定lfd的监听时间为"读"
 tep.events = EPOLLIN;
 tep.data.fd = listenfd;
 //将lfd及对应的结构体设置到树上,efd可找到该树
 int res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
 if(res == -1) {
 perror("epoll_ctl error");
 exit(1);
 }
 
 socklen_t clilen;
 struct sockaddr_in cliaddr;
 // ep[] : epoll_wait参数
 struct epoll_event ep[OPEN_MAX];
 char buf[MAXLINE], str[INET_ADDRSTRLEN];
 
 while(1) {
 /*epoll为server阻塞监听事件, ep为struct epoll_event类型数组, OPEN_MAX为数组容量, -1表永久阻塞*/
 int nready = epoll_wait(efd, ep, OPEN_MAX, -1);
 if(nready == -1) {
 perror("epoll_wait error");
 exit(1);
 }
 
 for (int i = 0; i < nready; i++) {
 //如果不是"读"事件, 继续循环
 if (!(ep[i].events & EPOLLIN)) {
 continue;
 }
 
 //判断满足事件的fd是不是lfd
 if (ep[i].data.fd == listenfd) {
 clilen = sizeof(cliaddr);
 // 接受连接请求
 int connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
 
 printf("received from %s at PORT %d\n",
 inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
 ntohs(cliaddr.sin_port));
 printf("cfd %d---client %d\n", connfd, ++num);
 
 tep.events = EPOLLIN;
 tep.data.fd = connfd;
 res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
 if(res == -1) {
 perror("epoll_ctl error");
 exit(1);
 }
 } else { // 不是监听的文件描述符, 通信的fd
 int sockfd = ep[i].data.fd;
 int n = read(sockfd, buf, MAXLINE);
 
 //读到0,说明客户端关闭链接
 if (n == 0) {
 //将该文件描述符从红黑树摘除
 res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
 if(res == -1) {
 perror("epoll_ctl error");
 exit(1);
 }
 //关闭与该客户端的链接
 close(sockfd);
 printf("client[%d] closed connection\n", sockfd);
 } else if (n < 0) { //出错
 perror("read n < 0 error: ");
 res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
 close(sockfd);
 } else { // 实际读到了字节数
 for (int j = 0; j < n; j++) {
 buf[j] = toupper(buf[j]); //转大写,写回给客户端
 }
 write(STDOUT_FILENO, buf, n); //发送到终端, 也可以打印出来
 write(sockfd, buf, n); //发送给客户端
 }
 }
 }
 }
 close(listenfd);
 close(efd);
 
 return 0;
}

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: