epoll用法

  在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。

epoll函数

1. 创建epoll的句柄

  size表示此内核监听的数目一共有多大,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

int epoll_create(int size);

2. epoll的事件注册函数

  它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  epfd:epoll_create()的返回值。

  op:表示动作,用三个宏来表示:

EPOLL_CTL_ADD     //注册新的fd到epfd中;
EPOLL_CTL_MOD //修改已经注册的fd的监听事件;
EPOLL_CTL_DEL //从epfd中删除一个fd;

  fd:需要监听的fd。

  event:告诉内核需要监听什么事,其结构如下:

//保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t; //感兴趣的事件和被触发的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

events可以是以下几个宏的集合:

EPOLLIN       //表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT //表示对应的文件描述符可以写;
EPOLLPRI //表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR //表示对应的文件描述符发生错误;
EPOLLHUP //表示对应的文件描述符被挂断;
EPOLLET //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT //只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

 3. 等待事件的产生

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

  events:用来从内核得到事件的集合

  maxevents:告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size

  timeout:超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)

  该函数返回需要处理的事件数目,如返回0表示已超时。

工作原理

  epoll只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射了(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

  另一个本质的改进在于采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

epoll优点

1.支持一个进程打开大数目的socket描述符(FD)

  select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于需要支持的上万连接数目的IM服务器来说显然太少,此时:

  一是:可以选择修改这个宏然后重新编译内核,不过这样会带来网络效率的下降;

  二是:可以选择多进程的解决方案,虽然linux创建进程的代价比较小,但仍旧不可忽视,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。

   epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048。举个例子在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

2.IO效率不随FD数目增加而线性下降

  传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。

  epoll不存在这个问题,它只会对"活跃"的socket进行操作 ----- 因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

3.使用mmap加速内核与用户空间的消息传递

  无论是select、poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。

4.内核微调

  这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。

使用

for( ; ; )
{
nfds = epoll_wait(epfd,events,,);
for(i=;i<nfds;++i)
{
if(events[i].data.fd==listenfd) //有新的连接
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中
} else if( events[i].events&EPOLLIN ) //接收到数据,读socket
{
n = read(sockfd, line, MAXLINE)) < //读
ev.data.ptr = md; //md为自定义类型,添加数据
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
}
else if(events[i].events&EPOLLOUT) //有数据待发送,写socket
{
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取数据
sockfd = md->fd;
send( sockfd, md->ptr, strlen((char*)md->ptr), ); //发送数据
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据
}
else
{
//其他的处理
}
}
}

举例:

#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h> using namespace std; #define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000 void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<)
{
perror("fcntl(sock,GETFL)");
exit();
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<)
{
perror("fcntl(sock,SETFL,opts)");
exit();
}
} int main(int argc, char* argv[])
{
int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
ssize_t n;
char line[MAXLINE];
socklen_t clilen; if ( == argc )
{
if( (portnumber = atoi(argv[])) < )
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[]);
return ;
}
}
else
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[]);
return ;
} //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
struct epoll_event ev,events[];
//生成用于处理accept的epoll专用的文件描述符
epfd=epoll_create();
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, );
//把socket设置为非阻塞方式
//setnonblocking(listenfd); //设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//ev.events=EPOLLIN;
//注册epoll事件 epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr="127.0.0.1";
inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber); serveraddr.sin_port=htons(portnumber);
bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = ;
for ( ; ; ) {
//等待epoll事件的发生
nfds=epoll_wait(epfd,events,,);
//处理所发生的所有事件
for(i=;i<nfds;++i)
{
if(events[i].data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
if(connfd<){
perror("connfd<0");
exit();
}
//setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
cout << "accapt a connection from " << str << endl;
//设置用于读操作的文件描述符
ev.data.fd=connfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//ev.events=EPOLLIN;
//注册ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if(events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
{
cout << "EPOLLIN" << endl;
if ( (sockfd = events[i].data.fd) < )
continue;
if ( (n = read(sockfd, line, MAXLINE)) < ) {
if (errno == ECONNRESET) {
close(sockfd);
events[i].data.fd = -;
} else
std::cout<<"readline error"<<std::endl;
} else if (n == ) {
close(sockfd);
events[i].data.fd = -;
}
line[n] = '/0';
cout << "read " << line << endl;
//设置用于写操作的文件描述符
ev.data.fd=sockfd;
//设置用于注测的写操作事件
ev.events=EPOLLOUT|EPOLLET;
//修改sockfd上要处理的事件为EPOLLOUT
//epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); }
else if(events[i].events&EPOLLOUT) // 如果有数据发送
{
sockfd = events[i].data.fd;
write(sockfd, line, n);
//设置用于读操作的文件描述符
ev.data.fd=sockfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//修改sockfd上要处理的事件为EPOLIN
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
}
}
return ;
}

网络通信 --> epoll用法的更多相关文章

  1. select,poll,epoll用法

    http://blog.csdn.net/sunboy_2050/article/details/6126712 select用法 #include <sys/time.h>       ...

  2. epoll用法【整理】

    l  epoll是什么? epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll 在Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO multiplex ...

  3. select、poll、epoll用法

    我们先从著名的C10K问题开始探讨,由于早期在网络还不普及的时候,互联网的用户并不是很多,一台服务器同时在线100个用户估计在当时已经算是大型应用了.但是随着互联网的发展,用户群体迅速的扩大,每一个用 ...

  4. 网络通信 --> select()用法

    select()用法 头文件 #include <sys/time.h> #include <sys/types.h> #include <unistd.h> 定义 ...

  5. linux epoll用法

    epoll 是 linux 特有的 I/O 复用函数.它是把用户关心的文件描述符事件放在内核的一个事件列表中,故而,无须像select和poll一样每次调用都重复传入文件描述符或事件集.但是, epo ...

  6. 【Python网络编程】epoll用法

    epoll发展进程 此处添加一下select.poll历程及其优缺点 原理 使用步骤 Create an epoll object--创建1个epoll对象 Tell the epoll object ...

  7. 【Redis】事件驱动框架源码分析

    aeEventLoop初始化 在server.c文件的initServer函数中,对aeEventLoop进行了初始化: 调用aeCreateEventLoop函数创建aeEventLoop结构体,对 ...

  8. 【Redis】事件驱动框架源码分析(单线程)

    aeEventLoop初始化 在server.c文件的initServer函数中,对aeEventLoop进行了初始化: 调用aeCreateEventLoop函数创建aeEventLoop结构体,对 ...

  9. epoll源码实现分析[整理]

    epoll用法回顾 先简单回顾下如何使用C库封装的3个epoll相关的系统调用.更详细的用法参见http://www.cnblogs.com/apprentice89/archive/2013/05/ ...

随机推荐

  1. Caused by: java.lang.ClassNotFoundException: Could not load requested class : org.h2.Driver

    1.错误描述 WARN:2015-05-01 13:26:10[localhost-startStop-1] - HHH000402: Using Hibernate built-in connect ...

  2. 不同WINDOWS平台下磁盘逻辑扇区的直接读写

    不同WINDOWS平台下磁盘逻辑扇区的直接读写 关键字:VWIN32.中断.DeviceIoControl 一.概述 在DOS操作系统下,通过BIOS的INT13.DOS的INT25(绝对读).INT ...

  3. FtpHelper ftp操作类库

    FtpHelper ftp操作类库 using System; using System.Collections.Generic; using System.Linq; using System.Te ...

  4. IO网络模型

    多路处理模型MPM MPM是Apache2引入的一个概念,就是将结构模块化.把核心任务处理作为一个可插拔的模块,使其能针对不同的环境进行优化 在这个情况下,就诞生出了处理模式的概念 Prefork 实 ...

  5. Django学习-14-分页功能实例

    首先创建一个制作page的工具类                     utils                         --page_make.py                    ...

  6. Excle巧取汉字打头的字串内容

    处理表格数据时遇到问题:从网页表格中复制来的数据,地址一列中出现类似于“4AWZCX万载老林业局2”的无用字符前缀.现在希望提取第一个汉字及其之后的所有内容(图1),寻求高效分离中英文及数字的实操方法 ...

  7. Request对象实现请求转发区别

    servlet请求转发与重定向的区别: request.setAttribute("test","hello"); request.getRequestDisp ...

  8. div里面的图片垂直居中

    外面一个div, 里面是一张大小不固定的图片, 或者span,div都行, 要实现里面元素水平垂直居中对齐 其实有很多实现的方法, 比如用js去计算每一个子元素的高度, 通过js去动态设置magin值 ...

  9. WPF中使用WebBrowser

    最近在做北京现代项目的时候,遇到一个需求将韩国那边写好的网页嵌套到WPF程序中显示. 开始的时候使用的是第三方的浏览器控件:awesomium,在本地测试,显示没有问题.但是拿到客户现场,只显示半屏. ...

  10. CF484E Sign on Fence

    题意 给定一个长度为n的数列,有m次询问,询问形如l r k 要你在区间[l,r]内选一个长度为k的区间,求区间最小数的最大值 Sol 二分答案 怎么判定,每种数字开一棵线段树 某个位置上的数大于等于 ...