epoll是在linux系统中,一种高效率的I/O复用技术.
在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。
相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
define __FD_SETSIZE 1024
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。
epoll的接口非常简单,一共就三个函数:
1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_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队列里
- int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
4、关于ET、LT两种工作模式:
可以得出这样的结论:
ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;而LT模式是只要有数据没有处理就会一直通知下去的.
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <vector>
#include <list>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>5
#define EPOLL_SIZE 1000
#define BUF_SIZE 1024
using namespace std;
//vector<int> fd_num;
list<int> fd_num; //保存客户端的描述符(操作系统将描述符抽象成一个int整数)
inline void error_hande(char *str); //输出错误信息
inline void SetNonblockingMode(int fd); //将描述符fd设置成边缘触发 。 epoll中的概念
int main(int argc, char *argv[])
{
char buf[50],buf2[50];
fd_num.clear();
int serv_sock,clnt_sock,epfd,event_num;
struct sockaddr_in serv_addr,clnt_addr;
socklen_t clt_sz = sizeof(clnt_addr);
struct epoll_event *e_events,event; //epoll
serv_sock = socket(PF_INET,SOCK_STREAM,0);
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8888);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1)
error_hande("bind() error!");
if(listen(serv_sock,10) == -1)
error_hande("listen() error!");
SetNonblockingMode(serv_sock); //设置成非阻塞
epfd = epoll_create(EPOLL_SIZE);
e_events = (struct epoll_event*)malloc(sizeof(struct epoll_event)*EPOLL_SIZE);
event.events = EPOLLIN;
event.data.fd = serv_sock;
epoll_ctl(epfd,EPOLL_CTL_ADD,serv_sock,&event);
while (true)
{
event_num = epoll_wait(epfd,e_events,EPOLL_SIZE,-1); //等待客户端的响应
if(event_num == -1)
{
puts("epoll_wait() error!"); break;
}
puts("epoll_wait()!");
for(int i=0; i<event_num; i++)
{
if(e_events[i].data.fd == serv_sock) //如果是本身的描述符响应了,就说明有新的客户端连接了
{
clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_addr,&clt_sz); //会将新的客户端的ip 端口绑定到clnt_sock
event.data.fd = clnt_sock;
event.events = EPOLLIN|EPOLLET;//发信息响应和边缘触发
SetNonblockingMode(clnt_sock); //设置边缘触发
epoll_ctl(epfd,EPOLL_CTL_ADD,clnt_sock,&event);
fd_num.push_back(clnt_sock); //加入队列
printf("new connect client: %d\n",clnt_sock);
}
else //如果是已有的客户端发信息了
{
while (true)
{
int str_len = read(e_events[i].data.fd,buf,BUF_SIZE); //读取信息
buf[str_len] = 0;
if(str_len == 0) //如果没有什么发来,就是要退出了
{
epoll_ctl(epfd,EPOLL_CTL_DEL,e_events[i].data.fd,NULL); //将这个描述符清除epoll
printf("close client: %d\n",e_events[i].data.fd);
close(e_events->data.fd);
fd_num.remove(e_events->data.fd); //删除
break;
}
else if(str_len<0 && errno==EAGAIN)
break;
else
{
sprintf(buf2,"message from client%d: %s",e_events[i].data.fd,buf);
list<int>::iterator ite;
for(ite=fd_num.begin(); ite!=fd_num.end(); ite++) //将信息发给每一个人
{
if(*ite != e_events[i].data.fd)
write(*ite,buf2,strlen(buf2));
}
}
}
}
}
}
close(epfd);
close(serv_sock);
return 0;
}
void error_hande(char *str)
{
fputs(str,stdout);
}
void SetNonblockingMode(int fd)
{
int flag = fcntl(fd,F_GETFL); //百度funtl , 边缘触发
fcntl(fd,F_SETFL,flag|O_NONBLOCK);
}