您的位置:首页 > 其它

epoll网路模型

2017-11-06 00:49 113 查看
引言:将自己封装epoll模型的收获记录下来,以便后续使用与改进

目录

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相关函数原型与结构体分析

函数所需要的头文件:#include

int  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 函数 模型