epoll网路模型
2017-11-06 00:49
113 查看
引言:将自己封装epoll模型的收获记录下来,以便后续使用与改进
目录
1、select、poll、epoll对比分析
2、epoll工作模式
3、epoll相关函数原型与结构体分析
4、epoll模型源码分析
select:
(1) 每次调用select都需要吧fd集合从用户态拷贝到内核态(此过程占用程序自身时间片),调用到内核态之后,采用轮询fd方式检测哪个fd有数据到来的信号,fd很大时开销很大。
(2) select对支持的文件描述符监视有数量限制,默认为1024.虽然可以进行对内核微调,但无法解决根本上的问题。
poll:
(1) poll比之select本质上差不多,都是采用内核轮询fd的方式,当fd很大时开销很大
(2) 比之select,poll没有1024的限制,理想状态下65535(系统允许打开的最大文件描述符数目),实际中受CPU、内存等限制
epoll:
(1) epoll为Linux下最好的并发模型,是select与poll模型的增强版本
(2) 更加的灵活,没有描述符的限制,理想状态下65535(系统允许打开的最大文件描述符数目),实际中受CPU、内存等限制,为了程序安全着想,我们需要限制下最大的连接数,防止cpu、内存占用率过高
(3) 使用一个描述符(epoll句柄)管理多个描述符
(4) 检测描述符信号时不是采用select与poll的内核轮询fd方式,而是采用的事件机制来管理(只要注册的事件有信号,内核立刻就能检测到)
(5) epoll通过epoll_wait,直接将有信号的事件存放到数组中,可以直接使用,而select与poll模型中只是返回有信号的数量,具体是谁有信号还需要我们去循环检测。
(1) 水平触发模式LT(默认的工作模式)
原理:当有数据来的时候,电平置高位(读完后置低位),只要fd的电平处于高位,epoll句柄就会标志出来,如果这次我们不去读数据,下次仍然会标志出这fd可读,直到我们读完为止。
举例说明:
如果epoll_wait的第二个参数(events)大小为10,此时有5个可读信号的到来,就把这5个的epoll_event信息填入 events中,然会返回5.
如果epoll_wait的第二个参数(events)大小为10,此时有35个可读信号的到来,就把这10个的epoll_event信息填入 events中,然会返回10.剩下的采用循环读取的方式再读取
(2) 边缘触发模式ET
原理:当fd有数据到来,电平从低位变为高位,此时epoll句柄就标志出这个fd有数据可读,如果这次不去读数据,即使下次fd有数据到来,epoll也不会标志出这个fd可读。因为边缘触发是根据电平的变化来标志可读,而数据到来的时候会置电平为高位,此时由于上次没有读取数据导致电平一直处在高位,电平也就不会发生变化,于是永远读取不到该fd的数据。使用
举例说明:
如果epoll_wait的第二个参数(events)大小为10,此时有15个可读信号的到来,由于数组的限制,就将这10个的epoll_event信息填入 events中,此时返回10.这10个fd的电平变为低位,等待下次数据的到来,剩下的5个fd的电平一直为高位。下次这5个fd有数据到来,也不会监视到这5个有数据到来。
功能:创建一个检测fd事件信号的epoll句柄,用于监视所有的fd描述符是否发生了相关事件。该函数其实在系统内核申请了一个size大小空间,该空间用于存放我们要监控的套接字fd,
返回值:返回一个epoll的文件描述符
size:监听的文件描述符个数(现在的版本里面已经废弃),所以填个1就行
功能:(事件注册函数)对套接字描述符注册要监听的事件,注册之后,内核进行监听事件工作,此过程不占用用户空间的时间片。(类似于Windows中的WSACreateEvent事件对象功能)
epfd:用于监视fd是否有数据到来的epoll句柄,即epoll_create的返回值
op:要进行的操作,操作的类型如:EPOLL_CTL_ADD|DEL|MOD(增删改)
fd:需要监听的fd
event:内核要监听的事件
data:联合体类型,一般填需要监听的fd
events:监听的事件类型,需要监听一个fd的多个事件时,通过或运算实现。例如EPOLLIN|EPOLLOUT
EPOLLIN fd可读
EPOLLOUT fd可写
EPOLLPRI fd紧急数据可读
EPOLLERR fd发生错误
EPOLLHUP fd 被挂起
EPOLLONESHOT fd 只监控 1 次,监控完后自动删除
EPOLLLT epoll 工作模式,设置为 水平触发模式
EPOLLET epoll 工作模式,设置为 边缘触发模式
功能:等待多个事件的发生,并把产生事件信号对应epoll_event结构体存放到events数组中,并返回待处理事件个数(类似于Windows中重叠IO模型中的WSAWaitForMultipleEvents)。这个过程为内核检测事件信号,不占用程序自身时间片。
返回值:超时返回0,正常返回待处理文件描述符个数,错误返回负数错误码
epfd:用于监视fd是否有数据到来的epoll句柄
events:存储从内核返回的待处理的事件集合
maxevents:所能存放的最大个数,防止越界
timeout:超时时间(毫秒,0立即返回,-1一直等待直到有事件发生)
void InitServerNet(NetCallBack func, int port, int bUdp = 0/是否添加udp/);
套路:初始化套接字、绑定、监听。这里UDP绑定的地址与TCP绑定的地址为同一个地址addr
int _beginEpoll(int bUdp);
通过epoll_create创建一个用于检测fd事件信号的epoll句柄,(AddMonitor)将tcp与udp服务端的套接字绑定EPOLLIN可读信号,并(epoll_ctl)注册检测事件。然后通过epoll_wait等待有事件信号到来,到来之后,将有信号的事件保存到events中,此时就开始我们的处理了…
注意:epoll_ctl注册事件之后,内核就开始检测事件信号了,然后通过epoll_wait将有信号的事件保存到events数组中。
void Epoll_Process(struct epoll_event* pevts, int num);
这里使用流程图展示
自己封装的epoll模型源码http://pan.baidu.com/s/1pK98cPX
不足之处望多多指正,共同进步
目录
1、select、poll、epoll对比分析
2、epoll工作模式
3、epoll相关函数原型与结构体分析
4、epoll模型源码分析
1、select、poll、epoll对比分析
性能测试图(网上扒的)select:
(1) 每次调用select都需要吧fd集合从用户态拷贝到内核态(此过程占用程序自身时间片),调用到内核态之后,采用轮询fd方式检测哪个fd有数据到来的信号,fd很大时开销很大。
(2) select对支持的文件描述符监视有数量限制,默认为1024.虽然可以进行对内核微调,但无法解决根本上的问题。
poll:
(1) poll比之select本质上差不多,都是采用内核轮询fd的方式,当fd很大时开销很大
(2) 比之select,poll没有1024的限制,理想状态下65535(系统允许打开的最大文件描述符数目),实际中受CPU、内存等限制
epoll:
(1) epoll为Linux下最好的并发模型,是select与poll模型的增强版本
(2) 更加的灵活,没有描述符的限制,理想状态下65535(系统允许打开的最大文件描述符数目),实际中受CPU、内存等限制,为了程序安全着想,我们需要限制下最大的连接数,防止cpu、内存占用率过高
(3) 使用一个描述符(epoll句柄)管理多个描述符
(4) 检测描述符信号时不是采用select与poll的内核轮询fd方式,而是采用的事件机制来管理(只要注册的事件有信号,内核立刻就能检测到)
(5) epoll通过epoll_wait,直接将有信号的事件存放到数组中,可以直接使用,而select与poll模型中只是返回有信号的数量,具体是谁有信号还需要我们去循环检测。
2、epoll工作模式
内核根据高低电平来判断fd是否有数据到来,高位表示有数据可读,低位表示无数据可读,电平的高低对应1和0的bit变化(1) 水平触发模式LT(默认的工作模式)
原理:当有数据来的时候,电平置高位(读完后置低位),只要fd的电平处于高位,epoll句柄就会标志出来,如果这次我们不去读数据,下次仍然会标志出这fd可读,直到我们读完为止。
举例说明:
如果epoll_wait的第二个参数(events)大小为10,此时有5个可读信号的到来,就把这5个的epoll_event信息填入 events中,然会返回5.
如果epoll_wait的第二个参数(events)大小为10,此时有35个可读信号的到来,就把这10个的epoll_event信息填入 events中,然会返回10.剩下的采用循环读取的方式再读取
(2) 边缘触发模式ET
原理:当fd有数据到来,电平从低位变为高位,此时epoll句柄就标志出这个fd有数据可读,如果这次不去读数据,即使下次fd有数据到来,epoll也不会标志出这个fd可读。因为边缘触发是根据电平的变化来标志可读,而数据到来的时候会置电平为高位,此时由于上次没有读取数据导致电平一直处在高位,电平也就不会发生变化,于是永远读取不到该fd的数据。使用
举例说明:
如果epoll_wait的第二个参数(events)大小为10,此时有15个可读信号的到来,由于数组的限制,就将这10个的epoll_event信息填入 events中,此时返回10.这10个fd的电平变为低位,等待下次数据的到来,剩下的5个fd的电平一直为高位。下次这5个fd有数据到来,也不会监视到这5个有数据到来。
3、epoll相关函数原型与结构体分析
函数所需要的头文件:#includeint epoll_create(int size);
功能:创建一个检测fd事件信号的epoll句柄,用于监视所有的fd描述符是否发生了相关事件。该函数其实在系统内核申请了一个size大小空间,该空间用于存放我们要监控的套接字fd,
返回值:返回一个epoll的文件描述符
size:监听的文件描述符个数(现在的版本里面已经废弃),所以填个1就行
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) ;
功能:(事件注册函数)对套接字描述符注册要监听的事件,注册之后,内核进行监听事件工作,此过程不占用用户空间的时间片。(类似于Windows中的WSACreateEvent事件对象功能)
epfd:用于监视fd是否有数据到来的epoll句柄,即epoll_create的返回值
op:要进行的操作,操作的类型如:EPOLL_CTL_ADD|DEL|MOD(增删改)
fd:需要监听的fd
event:内核要监听的事件
struct epoll_event { _uint32_t events; epoll_data_t data; } typedef union epoll_data { void *ptr; int fd; uint32 u32; uint64 u64; } epoll_data_t
data:联合体类型,一般填需要监听的fd
events:监听的事件类型,需要监听一个fd的多个事件时,通过或运算实现。例如EPOLLIN|EPOLLOUT
EPOLLIN fd可读
EPOLLOUT fd可写
EPOLLPRI fd紧急数据可读
EPOLLERR fd发生错误
EPOLLHUP fd 被挂起
EPOLLONESHOT fd 只监控 1 次,监控完后自动删除
EPOLLLT epoll 工作模式,设置为 水平触发模式
EPOLLET epoll 工作模式,设置为 边缘触发模式
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:等待多个事件的发生,并把产生事件信号对应epoll_event结构体存放到events数组中,并返回待处理事件个数(类似于Windows中重叠IO模型中的WSAWaitForMultipleEvents)。这个过程为内核检测事件信号,不占用程序自身时间片。
返回值:超时返回0,正常返回待处理文件描述符个数,错误返回负数错误码
epfd:用于监视fd是否有数据到来的epoll句柄
events:存储从内核返回的待处理的事件集合
maxevents:所能存放的最大个数,防止越界
timeout:超时时间(毫秒,0立即返回,-1一直等待直到有事件发生)
4、epoll模型源码分析
epoll.h#ifndef _EPOLL_MODEL_ #define _EPOLL_MODEL_ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <map> #endif #define MAX_LISTEN 100 // 监听的最大数量 #define MONNITOR_NUM 100 // epoll_wait一次检测的最大事件数量 #define MAX_CONN 4096 // 最大的连接数 #define MAX_LEN 1024 // 一次接收的最大字节数 typedef struct _FDINFO // 存储用户信息的结构体 { int fd; sockaddr_in addr; }FDINFO,*PFDINFO; typedef void(*NetCallBack) (FDINFO fdinfo, char* buf, int len); class epollModel { public: epollModel(); ~epollModel(); // 初始化网络 void InitServerNet(NetCallBack func, int port, int bUdp = 0/*是否添加udp*/); protected: // 消息处理函数 NetCallBack NetFunc; // 初始化服务端tcp套接字 int InitTcpServ(int port, const sockaddr_in& addr, socklen_t addrlen); // 初始化服务端udp套接字 int InitUdpServ(int port, const sockaddr_in& addr, socklen_t addrlen); // 给fd添加检测的事件,并加入检测 void AddMonitor(int fd); // 删除fd及注册的事件 void DelMonitor(std::map<int, struct sockaddr_in>::iterator it, struct epoll_event evt); // 开始epoll进行检测 int _beginEpoll(int bUdp); // 处理epoll检测到的事件 void Epoll_Process(struct epoll_event* pevts, int num); private: int nConn; // 连接数 int m_fdTcpServ; // tcp fd int m_fdUdpServ; // udp fd int m_fdEpoll; // epoll fd struct epoll_event m_evt; // epoll_event std::map<int, struct sockaddr_in> m_fdmap; // 存储用户信息 std::map<int, struct sockaddr_in>::iterator it; };
void InitServerNet(NetCallBack func, int port, int bUdp = 0/是否添加udp/);
void epollModel::InitServerNet(NetCallBack func, int port, int bUdp /*= 0*/) { NetFunc = func; // init net callback sockaddr_in addr; socklen_t addrlen = sizeof(addr); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (-1 == InitTcpServ(port, addr, addrlen)) return; if (bUdp == 0) // 是否启动udp监听 { if (-1 == InitUdpServ(port, addr, addrlen)) return; } _beginEpoll(bUdp); }
套路:初始化套接字、绑定、监听。这里UDP绑定的地址与TCP绑定的地址为同一个地址addr
int _beginEpoll(int bUdp);
int epollModel::_beginEpoll(int bUdp) { int iRet; m_fdEpoll = epoll_create(1); // 创建用于检测的epoll描述符 if (m_fdEpoll < 0) { perror("\rfail to epoll_create\n"); close(m_fdEpoll); return -1; } AddMonitor(m_fdTcpServ);// 使m_fdTcpServ被检测 if (bUdp == 0) AddMonitor(m_fdUdpServ);// 使m_fdUdpServ被检测 struct epoll_event evts[MONNITOR_NUM]; printf("\rserver start...\n"); while (1) { iRet = epoll_wait(m_fdEpoll, evts, MONNITOR_NUM, -1); if (iRet < 0) // epoll_wait error { perror("\rfail to epoll_wait\n"); continue; } if (iRet == 0) // timeout { continue; } Epoll_Process(evts, iRet); } }
通过epoll_create创建一个用于检测fd事件信号的epoll句柄,(AddMonitor)将tcp与udp服务端的套接字绑定EPOLLIN可读信号,并(epoll_ctl)注册检测事件。然后通过epoll_wait等待有事件信号到来,到来之后,将有信号的事件保存到events中,此时就开始我们的处理了…
注意:epoll_ctl注册事件之后,内核就开始检测事件信号了,然后通过epoll_wait将有信号的事件保存到events数组中。
void Epoll_Process(struct epoll_event* pevts, int num);
void epollModel::Epoll_Process(struct epoll_event *evts, int num) { int i; int newfd; int iRet; sockaddr_in addr; socklen_t addrlen = sizeof(addr); char szBuf[MAX_LEN]; FDINFO fdInfo; memset(&fdInfo, 0, sizeof(FDINFO)); for (i = 0; i < num; i++) { if (evts[i].data.fd == m_fdTcpServ) // 用户连接(tcp fd有信号) { newfd = accept(m_fdTcpServ, (struct sockaddr*)&addr, &addrlen); if (newfd < 0) { perror("\rfail to accept\n"); continue; } if (nConn == MAX_CONN) // 达到最大连接数 { write(newfd, "Over limit!", 12); printf("\rOver connect...\n"); close(newfd); continue; } m_fdmap.insert(std::make_pair(newfd, addr)); AddMonitor(newfd); printf("\rnew connect %d from [%s:%d]\n", newfd, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); nConn++; continue; } if (evts[i].data.fd == m_fdUdpServ) // udp 有信号 { memset(szBuf, 0, MAX_LEN); iRet = recvfrom(m_fdUdpServ, szBuf, MAX_LEN, 0, (struct sockaddr*)&addr, &addrlen); if (iRet > 0) { printf("\rUdp msg[%s:%d]:%s\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), szBuf); // 处理udp数据 ... sendto(m_fdUdpServ, szBuf, strlen(szBuf), 0, (struct sockaddr*)&addr, addrlen); } continue; } //////////////////////////////////////////////////////////////////// //// 客户端有数据信息到来 it = m_fdmap.find(evts[i].data.fd); if (it == m_fdmap.end()) // 判断fd是否在map中 { printf("\rUnknow client fd:%d\n", evts[i].data.fd); continue; } memset(szBuf, 0, MAX_LEN); // iRet = recv(evts[i].data.fd, szBuf, MAX_LEN, 0); iRet = read(evts[i].data.fd, szBuf, MAX_LEN);// 接收数据 recv或read都可以 if (iRet < 0) // 读取数据错误 { perror("\rfail to read\n"); continue; } if (iRet == 0) // 用户下线 { DelMonitor(it, evts[i]); printf("\rDisconnect fd:%d [%s:%d]\n", evts[i].data.fd, inet_ntoa((it->second).sin_addr), ntohs((it->second).sin_port)); continue; } fdInfo.fd = evts[i].data.fd; fdInfo.addr = it->second; NetFunc(fdInfo, szBuf, strlen(szBuf)); // 消息处理 } return; }
这里使用流程图展示
自己封装的epoll模型源码http://pan.baidu.com/s/1pK98cPX
不足之处望多多指正,共同进步
相关文章推荐
- Epoll模型详解
- Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)
- Epoll模型详解
- 多路复用I/O模型epoll() 模型 代码实现
- epoll模型有两种工作模式
- nginx采用epoll的事件模型,为何效率高
- select poll 与epoll模型的总结
- C-socket编-epoll()模型
- epoll模型中struct epoll_event中data联合体的用法
- Linux的epoll模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll、Epoll模型处理长连接性能比较
- IOCP模型与EPOLL模型的比较
- EPOLL事件有两种模型
- 网络模型,Epoll介绍,和几种其他模型的比较!
- select和epoll网络模型
- linux epoll 模型介绍及程序实例
- IOCP模型与EPOLL模型的比较
- linux epoll 模型详解
- C++ SOCKET通信模型(六)同步epoll
- IOCP模型、EPOLL模型的比较以及游戏服务器端的一些建议