LinuxI/O多路复用转接服务器——epoll模型实现

  • epoll函数
    • epoll函数组
      • epoll_create函数
      • epoll_ctl函数
      • epoll_wait函数
  • epoll实现实现I/O多路复用服务器
    • epoll模型
    • 程序实现
      • 服务端程序
      • 客户端程序
      • 运行结果
  • epoll事件模型
    • ET模式
    • LT模式
    • epoll的ET非阻塞模式(忙轮询)
      • 服务端程序
      • 客户端程序
    • 运行结果
  • epoll优缺点
  • epoll反应堆模型
  • select、poll、epoll对比分析

epoll函数

  epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
  epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

epoll函数组

epoll_create函数

原型:int epoll_create(int size)

作用:创建一个 epoll 对象,返回该对象的描述符,注意要使用 close 关闭该描述符。

参数:size:创建的红黑树的监听节点数量。(仅供内核参考)

返回值:

​  成功:指向新创建的红黑树的根结点的fd。

​​  失败:-1 errno

epoll_ctl函数

原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

作用:控制某个epoll监控的文件描述符上的事件:注册、修改、删除。

参数:
  epfd:epoll_creat的句柄(即epoll_create函数返回值)

​  op:对该监听红黑树所做的操作

         (1)EPOLL_CTL_ADD (添加fd到监听红黑树)​            (2)EPOLL_CTL_MOD (修改fd在监听红黑树上的监听事件)​          (3)EPOLL_CTL_DEL (将一个fd从监听红黑树上删除)

​  fd:待监听的fd

​  event:本质是struct epoll_event结构体的地址

struct epoll_event {__uint32_t events; /* Epoll events 【EPOLLIN,EPOLLOUT,EPOLLERR 等】*/epoll_data_t data; /* User data variable */
};
//联合体
typedef union epoll_data {void *ptr;//泛型指针(内核自动调回调函数)int fd;//对应监听事件uint32_t u32;uint64_t u64;} epoll_data_t;

返回值:
  成功:0

  失败:-1 errno

epoll_wait函数

原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

作用:等待所监控文件描述符上有事件的产生。

参数:

  ​events:用来存内核得到事件的集合。输出满足监听条件的fd结构体。【数组】

  ​maxevents:【数组元素的总个数】。告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size。

  ​timeout:超时时间

    -1:阻塞

    0:立即返回,非阻塞

    >0:指定毫秒

返回值:

​  大于0:有多少文件描述符就绪(满足监听的总个数,用作循环上限

  等于0:没有fd满足监听事件

  等于-1:失败 ,errno

epoll实现实现I/O多路复用服务器

epoll模型

  step1:创建套接字,绑定地址信息,设置监听上限;

  step2:创建监听红黑树(epoll_create),返回用作其他epoll系统调用的参数(文件描述符efd),用来指定访问的内核事件表;

  step3:将文件描述符fd上的注册事件添加到(事件表)红黑树上(epoll_ctl);

  step4:调用epoll_wait函数进行循环监听,监听到事件,将所有就绪事件从内核事件表(由efd指定)复制到传出数组;

  step5:判断返回数组元素,若监听套接字lfd满足,则等待客户端连接(accept),若数据通信套接字满足,则调用read函数读取数据并完成大小写转换,调用write函数写回。

程序实现

服务端程序

#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include "wrap.h"
using namespace std;
//定义服务端端口号
#define SERVER_PORT  9527
#define OPEN_MAX 1024int main (int argc ,char*argv[])
{int i,n;//int client[FD_SETSIZE];//自定义数组,大小为1024int nready=0;//保存epoll_wait函数返回值,记录满足监听事件的fd个数int lfd=0;//用于监听的套接字int cfd=0;//用于通信的套接字int sockfd=0;int efd=0;//用于接收epoll_create函数返回值int maxi;//用于检索客户端文件描述符的下标char buf[BUFSIZ],str[INET_ADDRSTRLEN];//创建epoll_event结构体   tep:epoll_ctl参数   ep[]: epoll_wait参数struct epoll_event tep ,ep[OPEN_MAX];//创建地址结构struct sockaddr_in server_addr,client_addr;socklen_t client_addr_len;//创建套接字lfd=Socket(AF_INET,SOCK_STREAM,0);//设置端口复用int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//初始化//memset(&server_addr,0,sizeof(server_addr));//将地址结构清零bzero(&server_addr,sizeof(server_addr));server_addr.sin_family=AF_INET;server_addr.sin_port=htons(SERVER_PORT);server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//绑定地址结构Bind(lfd,(struct sockaddr*)&server_addr,sizeof(server_addr));//设置监听Listen(lfd,128);//创建epoll模型 efd指向红黑树根节点efd=epoll_create(OPEN_MAX);//检查epoll_create函数返回值if(efd==-1){sys_err("epoll_create error");}//指定文件描述符的监听事件为“读”事件tep.events=EPOLLIN;tep.data.fd=lfd;//将lfd对应的结构体添加到红黑树上int ret;ret=epoll_ctl(efd,EPOLL_CTL_ADD,lfd,&tep);//判断返回值if(ret==-1){sys_err("epoll_ctl error");}//需要循环设置监听for(;;){//调用epoll_wait函数阻塞监听是否有客户端连接请求nready=epoll_wait(efd,ep,OPEN_MAX,-1);//检查是否成功返回if(nready==-1){sys_err("epoll_ctl error");}for(i=0;i<nready;i++){if(!(ep[i].events & EPOLLIN))//如果被“读”事件,继续循环{continue;}if(ep[i].data.fd==lfd)//判断满足事件的fd是否为lfd(监听事件){client_addr_len=sizeof(client_addr);cfd=Accept(lfd,(struct sockaddr*)&client_addr,&client_addr_len);cout<<"received from "<<inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str))<<"at PORT"<<ntohs(client_addr.sin_port)<<endl;tep.events=EPOLLIN;tep.data.fd=cfd;//将后续接收的请求返回的文件描述符(cfd)添加到红黑树中ret=epoll_ctl(efd,EPOLL_CTL_ADD,cfd,&tep);//判断返回值if(ret==-1){sys_err("epoll_ctl error");} }else//判断不是监听事件,则为数据读写事件{sockfd=ep[i].data.fd;n=read(sockfd,buf,sizeof(buf));//判断read函数返回值if(n==0)//对端关闭{//将文件描述符从红黑树中移除ret=epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);if(ret==-1){sys_err("epoll_ctl error");}Close(sockfd);}else if(n<0)//出错{ret=epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);Close(sockfd);}else{//实际读到的字节数for(i=0;i<n;i++){//大小写转换buf[i]=toupper(buf[i]);}//写回bufWrite(sockfd,buf,n);//写到屏幕输出Write(STDOUT_FILENO, buf, n); }}}  }Close(lfd);return 0;
}

客户端程序

同多进程客户端程序

运行结果

服务端:

客户端:

epoll事件模型

ET模式

Edge Triggered(边缘触发)工作模式

缓冲区剩余未读尽的数据不会导致epoll_wait返回,新的事件满足触发。

高速工作方式,只支持非阻塞no-block socket

LT模式

Level Triggered(电平触发)工作模式

缓冲区剩余未读尽的数据会导致epoll_wait返回

缺省工作方式,同时支持block和no-block socket

epoll的ET非阻塞模式(忙轮询)

服务端程序

#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include "wrap.h"
using namespace std;
//定义服务端端口号
#define SERVER_PORT  9527
#define MAXLINE 10
#define OPEN_MAX 1024int main (int argc ,char*argv[])
{int i,n;//int client[FD_SETSIZE];//自定义数组,大小为1024int nready=0;//保存epoll_wait函数返回值,记录满足监听事件的fd个数int lfd=0;//用于监听的套接字int cfd=0;//用于通信的套接字int sockfd=0;int efd=0;//用于接收epoll_create函数返回值int maxi;//用于检索客户端文件描述符的下标char buf[BUFSIZ],str[INET_ADDRSTRLEN];//创建epoll_event结构体   tep:epoll_ctl参数   ep[]: epoll_wait参数struct epoll_event tep ,ep[OPEN_MAX];//创建地址结构struct sockaddr_in server_addr,client_addr;socklen_t client_addr_len;//创建套接字lfd=Socket(AF_INET,SOCK_STREAM,0);//设置端口复用int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//初始化//memset(&server_addr,0,sizeof(server_addr));//将地址结构清零bzero(&server_addr,sizeof(server_addr));server_addr.sin_family=AF_INET;server_addr.sin_port=htons(SERVER_PORT);server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//绑定地址结构Bind(lfd,(struct sockaddr*)&server_addr,sizeof(server_addr));//设置监听Listen(lfd,128);struct epoll_event event;struct epoll_event resevent[10];int res, len;efd = epoll_create(10);event.events = EPOLLIN | EPOLLET;     /* ET 边沿触发 *///event.events = EPOLLIN;                 /* 默认 LT 水平触发 */cout<<"Accepting connections ..."<<endl;client_addr_len = sizeof(client_addr);cfd = accept(lfd, (struct sockaddr *)&client_addr, &client_addr_len);cout<<"received from "<<inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str))<<"at PORT"<<ntohs(client_addr.sin_port)<<endl;//设置cfd为非阻塞flag = fcntl(cfd,F_GETFL);flag |= O_NONBLOCK;fcntl(cfd, F_SETFL, flag);event.data.fd = cfd;epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &event);while (1) {res = epoll_wait(efd, resevent, 10, -1);cout<<"res:"<<res<<endl;if (resevent[0].data.fd == cfd) {len = read(cfd, buf, MAXLINE/2);         //readn(500)   write(STDOUT_FILENO, buf, len);}}return 0;
}

客户端程序

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>#define MAXLINE 10
#define SERV_PORT 9527
int main(int argc, char *argv[])
{struct sockaddr_in servaddr;char buf[MAXLINE];int sockfd, i;char ch = 'a';sockfd = socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);servaddr.sin_port = htons(SERV_PORT);connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));while (1) {//aaaa\nfor (i = 0; i < MAXLINE/2; i++)buf[i] = ch;buf[i-1] = '\n';ch++;//bbbb\nfor (; i < MAXLINE; i++)buf[i] = ch;buf[i-1] = '\n';ch++;//aaaa\nbbbb\nwrite(sockfd, buf, sizeof(buf));sleep(5);}close(sockfd);return 0;
}

运行结果


epoll优缺点

优点:

  高效,突破文件描述符上限1024,事件分离。

缺点:

  能跨平台,限于linux

epoll反应堆模型

  step1:创建套接字,绑定地址信息,设置监听上限;

  step2:创建监听红黑树(epoll_create),返回用作其他epoll系统调用的参数(文件描述符efd),用来指定访问的内核事件表;

  step3:将文件描述符fd上的注册事件添加到(事件表)红黑树上(epoll_ctl);

  step4:调用epoll_wait函数进行循环监听,监听到事件,将所有就绪事件从内核事件表(由efd指定)复制到传出数组;

  step5:判断返回数组元素,若监听套接字lfd满足,则等待客户端连接(accept),若cfd满足,则调用read函数读取数据并完成大小写转换;

  step6:将文件描述符cfd从监听红黑树上移除,将事件类型改为EPOLLOUT,调用epoll_ctl函数往(红黑树)事件表中添加事件并监听(写事件);

  step7:等待epoll_wait函数成功返回(说明cfd可写),调用write函数写回;

  step8:将文件描述符cfd从监听红黑树上移除,将事件类型改为EPOLLIN,调用epoll_ctl函数往(红黑树)事件表中添加事件并监听(读事件),调用epoll_wait函数进行监听,继续循环step4。

select、poll、epoll对比分析

系统调用 select poll epoll
事件集合 用户通过三个参数分别传入感兴趣的可读、可写、异常等事件,内核通过对这些参数的在线修改来反馈其中的就绪事件,使得每次调用select都需要重置三个参数 统一处理所有事件类型,用户只需要传通过pollfd.events传入感兴趣的事件,内核通过修改传入事件反馈其中的就绪事件 内核通过一个事件表直接管理用户感兴趣的所有事件,因此每次调用epoll_wait时,不需要反复传入用户感兴趣的事件。epoll_wait系统调用的参数events仅用来反馈就绪的事件
应用程序索引就绪文件描述符的时间复杂度 O(n) O(n) O(1)
最大支持的文件描述符数 一般有最大限制值 65535 65535
工作模式 LT LT 支持ET高效模式
内核实现和工作效率 采用轮询来检测就绪事件,算法时间复杂度为O(n) 采用轮询来检测就绪事件,算法时间复杂度为O(n) 采用回调函数来检测就绪事件,算法时间复杂度为O(1)

LinuxI/O多路复用转接服务器——epoll模型实现相关推荐

  1. LinuxI/O多路复用转接服务器——poll模型实现

    LinuxI/O多路复用转接服务器--poll模型实现 poll函数 函数原型 参数和返回值 poll实现实现I/O多路复用服务器 实现流程 程序实现 服务端程序 客户端程序 运行结果 poll优缺点 ...

  2. LinuxI/O多路复用转接服务器——select模型实现

    LinuxI/O多路复用转接服务器--select模型实现 select函数 函数原型 参数和返回值 fd_set结构体 位操作函数 select实现实现I/O多路复用服务器 实现流程 程序实现 服务 ...

  3. Linux网络服务器epoll模型的socket通讯的实现(一)

    准备写一个网络游戏的服务器的通讯模块,参考网上看到的一些代码,在linux下面实现一个多线程的epoll模型的socket通讯的代码,以下是第一部分多线程的切换代码: 1 #include <s ...

  4. 多路IO转接服务器 epoll

    创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关. #include <sys/epoll.h> int epoll_create(int size) ...

  5. 多路I/O转接服务器——epoll

    这里写目录标题 什么是epoll? epoll API 头文件 创建句柄 epoll控制函数 epoll消息读取 代码示例 什么是epoll? epoll接口是为解决Linux内核处理大量文件描述符而 ...

  6. epoll模型之服务器设计

    Linux  2.6内核中提高网络I/O性能的新方法-epoll I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数. 1.为什么select落后     首先,在L ...

  7. epoll服务器反应堆模型

    常规的epoll处理 epoll是io多路复用的一种实现方式,最开始我们使用epoll是对多个fd进行管理,当epoll_wait从内核的rdllist就绪链表中取出一定数量的poll_event时, ...

  8. IO多路复用之epoll模型

    一.IO多路复用:一个线程监测多个IO操作 基本思想:先构造一张有关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已经准备好进行I/O函数时才返回.函数返回时告诉进程哪个描述符已经就绪, ...

  9. java nio原理 epoll_多路复用 Select Poll Epoll 的实现原理(BIO与NIO)

    BIO blocking阻塞的意思,当我们在后端开发使用的时候,accetp 事件会阻塞主线程. 当accept事件执行的时候,客户的会和服务建立一个socket 连接.一般后端就会开启一个线程执行后 ...

最新文章

  1. HTML4如何让一个DIV居中对齐?float输入日志标题
  2. java下拉框选择_java中下拉框选项内容
  3. 一套房钱就可以做一个品牌的电子烟,让罗永浩们再赚一波快钱?
  4. python自动导出数据脚本_使用python生成一个导出数据库的bat脚本文件的示例代码...
  5. 观点 PK | 商用 PC or 消费 PC,开发者究竟该如何取舍?
  6. Linux转到Server服务,Linux下安装telnet-server服务
  7. 常用CNN网络(AlexNet,GoogleNet,VGG,ResNet,DenseNet,inceptionV4)适合初学者
  8. qt绘制运动物体_手写QT贪吃蛇,小白高薪捷径-Qt开发
  9. 敏感词检测理论算法过程
  10. Python 计算彩色图像信噪比
  11. Mac 安装ffmpeg 并使用ffmpeg将ts格式的文件转换成mp4
  12. SQLServer集群故障节点DISCONNECTED
  13. html字母上方加箭头,html5怎么在图片上加左右箭头
  14. Linux下安装window xp虚拟机
  15. 大数据--Hbase
  16. 性能最大提升60%,阿里云第八代企业级实例ECSg8i正式上线
  17. Linux内核的五大模块
  18. 使用Javascript Rhino重载Java方法
  19. 用Python来实现经典童年小游戏-- 贪 吃 蛇
  20. 股票新手入门-股票基本名词概念

热门文章

  1. 用计算机乘法怎么累加,乘法指令之: MLA乘-累加指令
  2. ERP系统中与BOM有关的常用方法
  3. 遭遇dns劫持会有哪些现象
  4. Visual C++程序设计——MFC整理笔记
  5. Java程序员学习资料分享,等你来收藏!
  6. HTML+CSS实现拼多多官网首页
  7. C#控制台实现飞行棋小游戏
  8. R语言学堂推文索引-2022年10月
  9. [程序员面试题精选100题]13.第一个只出现一次的字符
  10. matlab 系统辨识,matlab 系统辨识工具箱三分钟入手