epoll是linux内核所支持的网络系统调用。
核心数据结构
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 */
};
如:
struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
1.epoll_create函数
函数声明:int epoll_create(int size)该 函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。这个数字可以很大,可以为系统可以打开的最大文件数。cat /proc/sys/fs/file-max ,我们的机器上是655290
2. epoll_ctl函数
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数:
epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
fd:关联的文件描述符;
event:指向epoll_event的指针;
如果调用成功返回0,不成功返回-1
比如:
if((newId = accept(sd, (sockaddr *)&remote_addr, (socklen_t*)&sin_size)) < 0)
return -1;
// set receive time out
SetRecvTimeout(sd, timeout);
// add to comm epoll
{
epoll_event event;
event.events = EPOLLIN | EPOLLERR/* | EPOLLET*/;
event.data.fd = newId;
if(epoll_ctl(m_epfd, EPOLL_CTL_ADD, newId, &event) < 0 && errno != EEXIST)
{
TCP_SOCKET_CLOSE(sd);
return -1;
}
}
3.epoll_wait函数
函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事件的发生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值;-1相当于阻塞,0相当于非阻塞。一般用-1即可
返回发生事件数。
注意:epoll有2种工作方式:LT和ET。
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
man中给出了epoll的用法,example程序如下:
for(;;) {
nfds = epoll_wait(kdpfd, events, maxevents, -1);
for(n = 0; n < nfds; ++n) {
if(events[n].data.fd == listener) {
client = accept(listener, (struct sockaddr *) &local,
&addrlen);
if(client < 0){
perror("accept");
continue;
}
setnonblocking(client);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
fprintf(stderr, "epoll set insertion error: fd=%d\n",
client);
return -1;
}
}
else
do_use_fd(events[n].data.fd);
}
}
在do_use_fd函数中应该使用如下循环,才能将内核缓冲区中的数据读完。
while (1) {
len = recv(*******);
if (len == -1) {
if(errno == EAGAIN)
break;
perror("recv");
break;
}
do something with the recved data........
}
客户端发送流程示例:
1.m_epfdsend = epoll_create(MAX_SOCKET_COUNT);
2.send_sd;
3.epoll_event event;
event.events = EPOLLOUT | EPOLLET;
event.data.fd = send_fd;
epoll_ctl(m_epfdsend,EPOLL_CTL_ADD,send_fd,&event);
4.epoll_event events[MAX_SOCKET_COUNT];
int evcount = epoll_wait(m_epfdsend, &events, MAX_SOCKET_COUNT,time_out);
for(int i = 0; i < evcount;++i)
{
sd = events[i].data.fd;
evs = events[i].events;
send(sd, data, size, flag);
}
服务器端监听流程示例:
1.m_epfd = epoll_create(MAX_SOCKET_COUNT);
2.sd = socket(...);
setsockopt(sd, ...);
fcntl(sd, ...);
3.bind(sd,(sockaddr*)&addr, sizeof(addr));
4 listen(sd, 128);
5. epoll_event event;
event.data.fd = sd;
event.events = EPOLLIN | EPOLLERR;
epoll_ctl(m_epfd,EPOLL_CTL_ADD, sd, &event);
6. newid = accept(sd,(sock_addr*)&remote_addr, (socklen_t*)&sin_size);
setsockopt(sd, ...);
epoll_event event;
event.events = EPOLLIN|EPOLLERR;
event.data.fd = newid;
epoll_ctl(m_epfd, EPOLL_CTL_ADD, newid, &event);