LinuxI/O多路复用转接服务器——epoll模型实现
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模型实现相关推荐
- LinuxI/O多路复用转接服务器——poll模型实现
LinuxI/O多路复用转接服务器--poll模型实现 poll函数 函数原型 参数和返回值 poll实现实现I/O多路复用服务器 实现流程 程序实现 服务端程序 客户端程序 运行结果 poll优缺点 ...
- LinuxI/O多路复用转接服务器——select模型实现
LinuxI/O多路复用转接服务器--select模型实现 select函数 函数原型 参数和返回值 fd_set结构体 位操作函数 select实现实现I/O多路复用服务器 实现流程 程序实现 服务 ...
- Linux网络服务器epoll模型的socket通讯的实现(一)
准备写一个网络游戏的服务器的通讯模块,参考网上看到的一些代码,在linux下面实现一个多线程的epoll模型的socket通讯的代码,以下是第一部分多线程的切换代码: 1 #include <s ...
- 多路IO转接服务器 epoll
创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关. #include <sys/epoll.h> int epoll_create(int size) ...
- 多路I/O转接服务器——epoll
这里写目录标题 什么是epoll? epoll API 头文件 创建句柄 epoll控制函数 epoll消息读取 代码示例 什么是epoll? epoll接口是为解决Linux内核处理大量文件描述符而 ...
- epoll模型之服务器设计
Linux 2.6内核中提高网络I/O性能的新方法-epoll I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数. 1.为什么select落后 首先,在L ...
- epoll服务器反应堆模型
常规的epoll处理 epoll是io多路复用的一种实现方式,最开始我们使用epoll是对多个fd进行管理,当epoll_wait从内核的rdllist就绪链表中取出一定数量的poll_event时, ...
- IO多路复用之epoll模型
一.IO多路复用:一个线程监测多个IO操作 基本思想:先构造一张有关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已经准备好进行I/O函数时才返回.函数返回时告诉进程哪个描述符已经就绪, ...
- java nio原理 epoll_多路复用 Select Poll Epoll 的实现原理(BIO与NIO)
BIO blocking阻塞的意思,当我们在后端开发使用的时候,accetp 事件会阻塞主线程. 当accept事件执行的时候,客户的会和服务建立一个socket 连接.一般后端就会开启一个线程执行后 ...
最新文章
- HTML4如何让一个DIV居中对齐?float输入日志标题
- java下拉框选择_java中下拉框选项内容
- 一套房钱就可以做一个品牌的电子烟,让罗永浩们再赚一波快钱?
- python自动导出数据脚本_使用python生成一个导出数据库的bat脚本文件的示例代码...
- 观点 PK | 商用 PC or 消费 PC,开发者究竟该如何取舍?
- Linux转到Server服务,Linux下安装telnet-server服务
- 常用CNN网络(AlexNet,GoogleNet,VGG,ResNet,DenseNet,inceptionV4)适合初学者
- qt绘制运动物体_手写QT贪吃蛇,小白高薪捷径-Qt开发
- 敏感词检测理论算法过程
- Python 计算彩色图像信噪比
- Mac 安装ffmpeg 并使用ffmpeg将ts格式的文件转换成mp4
- SQLServer集群故障节点DISCONNECTED
- html字母上方加箭头,html5怎么在图片上加左右箭头
- Linux下安装window xp虚拟机
- 大数据--Hbase
- 性能最大提升60%,阿里云第八代企业级实例ECSg8i正式上线
- Linux内核的五大模块
- 使用Javascript Rhino重载Java方法
- 用Python来实现经典童年小游戏-- 贪 吃 蛇
- 股票新手入门-股票基本名词概念