注:内容皆是摘录
Linux 2.6内核完全支持epoll.
epoll的IO效率不随FD数目增加而线性下降
传统的select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。
内核实现中epoll是根据每个fd上面的callback函数实现的。只有"活跃"的socket才会主
动的去调用 callback函数,其他idle状态socket则不会。
如果所有的socket基本上都是活跃的---比如一个高速LAN环境,过多使用epoll,效率
相比还有稍微的下降。
但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
poll的执行分三部分:
1.将用户传入的pollfd数组拷贝到内核空间,因为拷贝操作和数组长度相关,时间
上这是一个O(n)操作
2.
查询每个文件描述符对应设备的状态,如果该设备尚未就绪,则在该设备的等
待队列中加入一项并继续查询下一设备的状态。
查询完所有设备后如果没有一个设备就绪,这时则需要挂起当前进程等待,直
到设备就绪或者超时。
设备就绪后进程被通知继续运行,这时再次遍历所有设备,以查找就绪设备。
这一步因为两次遍历所有设备,时间复杂度也是O(n),这里面不包括等待时
间。。
3.
将获得的数据传送到用户空间并执行释放内存和剥离等待队列等善后工作,向
用户空间拷贝数据与剥离等待队列等操作的的时间复杂度同样是O(n)。
epoll用到的所有函数都是在头文件sys/epoll.h中声明的,下面简要说明所用到的数
据结构和函数:
所用到的数据结构
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 */
};
结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件.
其中epoll_data 联合体用来保存触发事件的某个文件描述符相关的数据.
例如一个client连接到服务器,服务器通过调用accept函数可以得到于这个client对
应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段以便后面的读
写操作在这个文件描述符上进行。epoll_event 结构体的events字段是表示感兴趣的
事件和被触发的事件可能的取值为:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
所用到的函数:
1、int epoll_create(int size)
该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大
范围
2、
用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
如果调用成功返回0,不成功返回-1
int epoll_ctl(
int epfd,//由 epoll_create 生成的epoll专用的文件描述符
int op,//要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、
EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除
int fd,//关联的文件描述符
struct epoll_event *event//指向epoll_event的指针
)
3、
用于轮询I/O事件的发生
返回发生事件数
int epoll_wait(
int epfd,//由epoll_create 生成的epoll专用的文件描述符
struct epoll_event * events,//用于回传代处理事件的数组
int maxevents,//每次能处理的事件数
int timeout//等待I/O事件发生的超时值
//为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件
//为任意正整数的时候表示等这么长的时间,如果一直没有事件
//一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以
保证一些效率
//如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率
。
)
用法示例
[code]
struct epoll_event ev, *events;
for(;;) {
nfds = epoll_wait(kdpfd, events, maxevents, -1);/*wait for an I/O event. */
for(n = 0; n < nfds; ++n) {
if(events[n].data.fd == listener) {/*if listen socket has an I/O, accept the new connect*/
client = accept(listener, (struct sockaddr *) &local,
&addrlen);
if(client < 0){
perror("accept");
continue;
}
setnonblocking(client);
ev.events = EPOLLIN | EPOLLET;/*EPOLLIN-available for read*/
ev.data.fd = client;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {/*add the new socket into the
epoll file descriptors*/
fprintf(stderr, "epoll set insertion error: fd=%d\n",
client);
return -1;
}
}
else
do_use_fd(events[n].data.fd);/*read from a socket which has data come*/
}
}
[/code]
代码2[code]
static int s_epfd;//epoll描述字
{//初始化epoll
struct epoll_event ev;
//设置epoll
s_epfd = epoll_create(65535);
{//这个过程可以循环以便加入多个LISTEN套接字进入epoll事件集合
//服务器监听创建
rc = listen();//listen参数这里省略
//加入epoll事件集合
ev.events = EPOLLIN;
ev.data.fd = rc;
if (epoll_ctl(s_epfd, EPOLL_CTL_ADD, rc, &ev) < 0) {
fprintf(stderr, "epoll set insertion error: fd=%d", rc);
return(-1);
}
}
}
{//epoll事件处理
int i, nfds, sock_new;
struct epoll_event events[16384];
for( ; ; ) {
//等待epoll事件
nfds = epoll_wait(s_epfd, events, 16384, -1);
//处理epoll事件
for(i = 0; i < nfds; i++) {
//events[i].data.fd是epoll事件中弹出的套接字
//接收连接
sock_new = accept(events[i].data.fd);//accept其它参数这里省略了
if(0 > sock_new) {
fprintf(stderr, "接收客户端连接失败\n");
continue;
}
}
}
}
[/code]
代码3
[code]#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>
#define MAXLINE 10
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5555
#define INFTIM 1000
void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main()
{
int i, maxi, listenfd, connfd, sockfd,epfd,nfds;
ssize_t n;
char line[MAXLINE];
socklen_t clilen;
//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
struct epoll_event ev,events[20];
//生成用于处理accept的epoll专用的文件描述符
epfd=epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//把socket设置为非阻塞方式
setnonblocking(listenfd);
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr="200.200.200.204";
inet_aton(local_addr,&(serveraddr.sin_addr));//htons(SERV_PORT);
serveraddr.sin_port=htons(SERV_PORT);
bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
for ( ; ; ) {
//等待epoll事件的发生
nfds=epoll_wait(epfd,events,20,500);
//处理所发生的所有事件
for(i=0;i<nfds;++i)
{
if(events[i].data.fd==listenfd)
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
if(connfd<0){
perror("connfd<0");
exit(1);
}
setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
std::cout<<"connect from "<_u115 ?tr<<std::endl;
//设置用于读操作的文件描述符
ev.data.fd=connfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//注册ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if(events[i].events&EPOLLIN)
{
if ( (sockfd = events[i].data.fd) < 0) continue;
if ( (n = read(sockfd, line, MAXLINE)) < 0) {
if (errno == ECONNRESET) {
close(sockfd);
events[i].data.fd = -1;
} else
std::cout<<"readline error"<<std::endl;
} else if (n == 0) {
close(sockfd);
events[i].data.fd = -1;
}
//设置用于写操作的文件描述符
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);
}
}
}
}[/code]
Linux 2.6内核完全支持epoll.
epoll的IO效率不随FD数目增加而线性下降
传统的select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。
内核实现中epoll是根据每个fd上面的callback函数实现的。只有"活跃"的socket才会主
动的去调用 callback函数,其他idle状态socket则不会。
如果所有的socket基本上都是活跃的---比如一个高速LAN环境,过多使用epoll,效率
相比还有稍微的下降。
但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
poll的执行分三部分:
1.将用户传入的pollfd数组拷贝到内核空间,因为拷贝操作和数组长度相关,时间
上这是一个O(n)操作
2.
查询每个文件描述符对应设备的状态,如果该设备尚未就绪,则在该设备的等
待队列中加入一项并继续查询下一设备的状态。
查询完所有设备后如果没有一个设备就绪,这时则需要挂起当前进程等待,直
到设备就绪或者超时。
设备就绪后进程被通知继续运行,这时再次遍历所有设备,以查找就绪设备。
这一步因为两次遍历所有设备,时间复杂度也是O(n),这里面不包括等待时
间。。
3.
将获得的数据传送到用户空间并执行释放内存和剥离等待队列等善后工作,向
用户空间拷贝数据与剥离等待队列等操作的的时间复杂度同样是O(n)。
epoll用到的所有函数都是在头文件sys/epoll.h中声明的,下面简要说明所用到的数
据结构和函数:
所用到的数据结构
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 */
};
结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件.
其中epoll_data 联合体用来保存触发事件的某个文件描述符相关的数据.
例如一个client连接到服务器,服务器通过调用accept函数可以得到于这个client对
应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段以便后面的读
写操作在这个文件描述符上进行。epoll_event 结构体的events字段是表示感兴趣的
事件和被触发的事件可能的取值为:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
所用到的函数:
1、int epoll_create(int size)
该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大
范围
2、
用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
如果调用成功返回0,不成功返回-1
int epoll_ctl(
int epfd,//由 epoll_create 生成的epoll专用的文件描述符
int op,//要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、
EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除
int fd,//关联的文件描述符
struct epoll_event *event//指向epoll_event的指针
)
3、
用于轮询I/O事件的发生
返回发生事件数
int epoll_wait(
int epfd,//由epoll_create 生成的epoll专用的文件描述符
struct epoll_event * events,//用于回传代处理事件的数组
int maxevents,//每次能处理的事件数
int timeout//等待I/O事件发生的超时值
//为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件
//为任意正整数的时候表示等这么长的时间,如果一直没有事件
//一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以
保证一些效率
//如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率
。
)
用法示例
[code]
struct epoll_event ev, *events;
for(;;) {
nfds = epoll_wait(kdpfd, events, maxevents, -1);/*wait for an I/O event. */
for(n = 0; n < nfds; ++n) {
if(events[n].data.fd == listener) {/*if listen socket has an I/O, accept the new connect*/
client = accept(listener, (struct sockaddr *) &local,
&addrlen);
if(client < 0){
perror("accept");
continue;
}
setnonblocking(client);
ev.events = EPOLLIN | EPOLLET;/*EPOLLIN-available for read*/
ev.data.fd = client;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {/*add the new socket into the
epoll file descriptors*/
fprintf(stderr, "epoll set insertion error: fd=%d\n",
client);
return -1;
}
}
else
do_use_fd(events[n].data.fd);/*read from a socket which has data come*/
}
}
[/code]
代码2[code]
static int s_epfd;//epoll描述字
{//初始化epoll
struct epoll_event ev;
//设置epoll
s_epfd = epoll_create(65535);
{//这个过程可以循环以便加入多个LISTEN套接字进入epoll事件集合
//服务器监听创建
rc = listen();//listen参数这里省略
//加入epoll事件集合
ev.events = EPOLLIN;
ev.data.fd = rc;
if (epoll_ctl(s_epfd, EPOLL_CTL_ADD, rc, &ev) < 0) {
fprintf(stderr, "epoll set insertion error: fd=%d", rc);
return(-1);
}
}
}
{//epoll事件处理
int i, nfds, sock_new;
struct epoll_event events[16384];
for( ; ; ) {
//等待epoll事件
nfds = epoll_wait(s_epfd, events, 16384, -1);
//处理epoll事件
for(i = 0; i < nfds; i++) {
//events[i].data.fd是epoll事件中弹出的套接字
//接收连接
sock_new = accept(events[i].data.fd);//accept其它参数这里省略了
if(0 > sock_new) {
fprintf(stderr, "接收客户端连接失败\n");
continue;
}
}
}
}
[/code]
代码3
[code]#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>
#define MAXLINE 10
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5555
#define INFTIM 1000
void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main()
{
int i, maxi, listenfd, connfd, sockfd,epfd,nfds;
ssize_t n;
char line[MAXLINE];
socklen_t clilen;
//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
struct epoll_event ev,events[20];
//生成用于处理accept的epoll专用的文件描述符
epfd=epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//把socket设置为非阻塞方式
setnonblocking(listenfd);
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr="200.200.200.204";
inet_aton(local_addr,&(serveraddr.sin_addr));//htons(SERV_PORT);
serveraddr.sin_port=htons(SERV_PORT);
bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
for ( ; ; ) {
//等待epoll事件的发生
nfds=epoll_wait(epfd,events,20,500);
//处理所发生的所有事件
for(i=0;i<nfds;++i)
{
if(events[i].data.fd==listenfd)
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
if(connfd<0){
perror("connfd<0");
exit(1);
}
setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
std::cout<<"connect from "<_u115 ?tr<<std::endl;
//设置用于读操作的文件描述符
ev.data.fd=connfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//注册ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if(events[i].events&EPOLLIN)
{
if ( (sockfd = events[i].data.fd) < 0) continue;
if ( (n = read(sockfd, line, MAXLINE)) < 0) {
if (errno == ECONNRESET) {
close(sockfd);
events[i].data.fd = -1;
} else
std::cout<<"readline error"<<std::endl;
} else if (n == 0) {
close(sockfd);
events[i].data.fd = -1;
}
//设置用于写操作的文件描述符
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);
}
}
}
}[/code]