epoll是Linux内核为处理大批句柄而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本,
它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,
另一个原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
epoll除了提供select\poll那种IO事件的电平触发(Level Triggered)外,
还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提供应用程序的效率。
与select相比
epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。
并且,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE 1024
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。
工作方式:
LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket。
在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,
你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,
所以,这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。
ET(edge-triggered):边沿触发,高速工作方式,只支持no-block socket。
在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。
然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,
直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。
但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。
区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。
1、int epoll_create(int size)
创建一个epoll句柄,参数size用来告诉内核监听的数目。
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epoll事件注册函数,
参数epfd为epoll的句柄;
参数op表示动作,用3个宏来表示:EPOLL_CTL_ADD(注册新的fd到epfd),EPOLL_CTL_MOD(修改已经注册的fd的监听事件),EPOLL_CTL_DEL(从epfd删除一个fd);
参数fd为需要监听的标示符;
参数event告诉内核需要监听的事件,event的结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
其中events可以用以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT:表示对应的文件描述符可以写
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR:表示对应的文件描述符发生错误
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合
maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
参数timeout是超时时间(毫秒,0会立即返回,-1 将不确定,也有说法说是永久阻塞)。
该函数返回需要处理的事件数目,如返回0表示已超时。
使用示例
采用epoll创建服务器
服务器可以接收客户端信息,然后群发给客户端
服务器可以接收终端信息,然后群发给客户端
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include <sys/epoll.h>
#include<list>
#include <fcntl.h>
#include <ctime>
using namespace std;
#define BUF_SIZE 1024 //默认缓冲区
#define SERVER_PORT 3000 //监听端口
#define SERVER_HOST "127.0.0.1" //服务器IP地址
#define EPOLL_RUN_TIMEOUT -1 //epoll的超时时间
#define EPOLL_SIZE 10000 //epoll监听的客户端的最大数目
#define STR_WELCOME "Welcome to Chat! You ID is: Client #%d"
#define STR_MESSAGE "Client #%d send to server is %s"
#define STR_NOONE_CONNECTED "No one connected to server except you!"
#define CMD_EXIT "EXIT"
#define CHK(eval) if(eval < 0){perror("eval"); exit(-1);}
#define CHK2(res, eval) if((res = eval) < 0){perror("eval"); exit(-1);}
using namespace std;
// 存放客户端socket描述符的list
list<int> clients_list;
int setnonblocking(int sockfd)
{
CHK(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK));
return 0;
}
int handle_message(int client)
{
char buf[BUF_SIZE], message[BUF_SIZE];
bzero(buf, BUF_SIZE);
bzero(message, BUF_SIZE);
int len;
CHK2(len,recv(client, buf, BUF_SIZE, 0)); //接受客户端信息
if(len == 0) //客户端关闭或出错,关闭socket,并从list移除socket
{
CHK(close(client));
clients_list.remove(client);
}
else //向客户端发送信息
{
if(clients_list.size() == 1)
{
CHK(send(client, STR_NOONE_CONNECTED, strlen(STR_NOONE_CONNECTED), 0));
return len;
}
sprintf(message, STR_MESSAGE, client, buf);
list<int>::iterator it;
for(it = clients_list.begin(); it != clients_list.end(); it++)
{
if(*it != client)
{
printf("server send %s\n",message);
CHK(send(*it, message, BUF_SIZE, 0));
}
}
}
return len;
}
int main(int argc,char*argv[])
{
int listener;
struct sockaddr_in seraddr,cliaddr;
seraddr.sin_family = PF_INET;
seraddr.sin_port = htons(SERVER_PORT);
seraddr.sin_addr.s_addr = inet_addr(SERVER_HOST);
socklen_t socklen;
socklen = sizeof(struct sockaddr_in);
static struct epoll_event ev,events[EPOLL_SIZE];
ev.events = EPOLLIN | EPOLLET;
char message[BUF_SIZE];
int epfd;
clock_t tStart;
int client,res,epoll_events_count;
CHK2(listener,socket(PF_INET,SOCK_STREAM,0));
setnonblocking(listener);
CHK(bind(listener,(struct sockaddr*)&seraddr,sizeof(seraddr)));
CHK(listen(listener,1));
CHK2(epfd,epoll_create(EPOLL_SIZE));
ev.data.fd = listener;
CHK(epoll_ctl(epfd,EPOLL_CTL_ADD,listener,&ev));
ev.data.fd = 0;
CHK(epoll_ctl(epfd,EPOLL_CTL_ADD,0,&ev));
printf("server begin ip [%s],port [%d]\n",inet_ntoa(seraddr.sin_addr),ntohs(seraddr.sin_port));
while(1)
{
CHK2(epoll_events_count,epoll_wait(epfd,events,EPOLL_SIZE,EPOLL_RUN_TIMEOUT));
tStart = clock();
printf("epoll_events_count = [%d]\n",epoll_events_count);
for(int i=0;i < epoll_events_count;i++)
{
if(listener == events[i].data.fd )
{
CHK2(client,accept(listener,(struct sockaddr*)&cliaddr,&socklen));
printf("new client ip [%s],port [%d]\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
setnonblocking(client);
ev.data.fd = client;
CHK(epoll_ctl(epfd,EPOLL_CTL_ADD,client,&ev));
clients_list.push_back(client);
bzero(message,BUF_SIZE);
res = sprintf(message,STR_WELCOME,client);
CHK2(res,send(client,message,BUF_SIZE,0));
}
else if(events[i].data.fd == 0)
{
char buf1[BUF_SIZE];
bzero(buf1,BUF_SIZE + 1);
fgets(buf1,BUF_SIZE,stdin);
char buf[BUF_SIZE];
bzero(buf,BUF_SIZE + 1);
sprintf(buf,"server send to all client %s",buf1);
if(!strncasecmp(buf,"quit",4))
{
printf("i will quit!\n");
break;
}
list<int>::iterator it;
for(it = clients_list.begin(); it != clients_list.end(); it++)
{
CHK(send(*it, buf, BUF_SIZE, 0));
}
}
else
{
CHK2(res,handle_message(events[i].data.fd));
}
}
}
close(listener);
close(epfd);
return 0;
}
客户端程序
客户端可以接收服务器信息,还可以发送信息给服务器。
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include <sys/epoll.h>
#include<list>
#include <fcntl.h>
#include <ctime>
#define BUF_SIZE 1024
#define SERVER_PORT 3000
#define SERVER_HOST "127.0.0.1"
#define EPOLL_RUN_TIMEOUT -1
#define EPOLL_SIZE 10000
#define CMD_EXIT "EXIT"
#define CHK(eval) if(eval < 0){perror("eval"); exit(-1);}
#define CHK2(res, eval) if((res = eval) < 0){perror("eval"); exit(-1);}
using namespace std;
char message[BUF_SIZE];
int main(int argc,char* argv[])
{
int sock,pid,pipefd[2],epfd;
struct sockaddr_in cliaddr;
cliaddr.sin_family = PF_INET;
cliaddr.sin_port = htons(SERVER_PORT);
cliaddr.sin_addr.s_addr = inet_addr(SERVER_HOST);
static struct epoll_event ev,events[2];
ev.events = EPOLLIN | EPOLLET;
int continuetowork = 1;
CHK2(sock,socket(PF_INET,SOCK_STREAM,0));
CHK(connect(sock,(struct sockaddr*)&cliaddr,sizeof(cliaddr)) <0);
CHK(pipe(pipefd));
CHK2(epfd,epoll_create(EPOLL_SIZE));
ev.data.fd = sock;
CHK(epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&ev));
ev.data.fd = pipefd[0];
CHK(epoll_ctl(epfd,EPOLL_CTL_ADD,pipefd[0],&ev));
CHK2(pid,fork());
switch(pid)
{
case 0:
close(pipefd[0]);
printf("enter 'exit' to exit\n");
while(continuetowork)
{
bzero(&message,BUF_SIZE);
fgets(message,BUF_SIZE,stdin);
if(strncasecmp(message,CMD_EXIT,strlen(CMD_EXIT)) == 0)
{
continuetowork = 0;
}
else
{
CHK(write(pipefd[1],message,strlen(message) -1));
}
}
break;
default:
close(pipefd[1]);
int epoll_events_count,res;
while(continuetowork)
{
CHK2(epoll_events_count,epoll_wait(epfd,events,2,EPOLL_RUN_TIMEOUT));
for(int i =0;i<epoll_events_count;i++)
{
bzero(&message,BUF_SIZE);
if(events[i].data.fd == sock)
{
CHK2(res,recv(sock,message,BUF_SIZE,0));
if(res == 0)
{
CHK(close(sock));
continuetowork=0;
}
else
{
printf("client recv %s\n",message);
}
}
else
{
CHK2(res,read(events[i].data.fd,message,BUF_SIZE));
if(res == 0)
{
continuetowork=0;
}
else
{
printf("client send %s\n",message);
CHK(send(sock,message,BUF_SIZE,0));
}
}
}
}
}
if(pid)
{
close(pipefd[0]);
close(sock);
}
else
{
close(pipefd[1]);
}
return 0;
}
测试服务器最大可以接收客户端数量
程序如下
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include <sys/epoll.h>
#include<list>
#include <fcntl.h>
#include <ctime>
#define BUF_SIZE 1024 //默认缓冲区
#define SERVER_PORT 3000 //监听端口
#define SERVER_HOST "127.0.0.1" //服务器IP地址
#define EPOLL_RUN_TIMEOUT -1 //epoll的超时时间
#define EPOLL_SIZE 10000 //epoll监听的客户端的最大数目
#define CHK(eval) if(eval < 0){perror("eval"); exit(-1);}
#define CHK2(res, eval) if((res = eval) < 0){perror("eval"); exit(-1);}
using namespace std;
char message[BUF_SIZE];
list<int>list_of_clients;
int res;
clock_t tStart;
int main(int argc,char* argv[])
{
int sock;
struct sockaddr_in cliaddr;
cliaddr.sin_family = PF_INET;
cliaddr.sin_port = htons(SERVER_PORT);
cliaddr.sin_addr.s_addr = inet_addr(SERVER_HOST);
tStart = clock();
for(int i=0;i<EPOLL_SIZE;i++)
{
CHK2(sock,socket(PF_INET,SOCK_STREAM,0));
CHK(connect(sock,(struct sockaddr*)&cliaddr,sizeof(cliaddr)) < 0);
list_of_clients.push_back(sock);
bzero(&message,BUF_SIZE);
CHK2(res,recv(sock,message,BUF_SIZE,0));
printf("client [%d] recve [%s]\n",sock,message);
}
list<int>::iterator it;
for(it = list_of_clients.begin(); it != list_of_clients.end();it++ )
close(*it);
printf("Test passed at: %.2f second(s)\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
printf("Total server connections was: %d\n", EPOLL_SIZE);
return 0;
}