epoll对应的三个主要函数:
1、int epoll_create(int size)与int epoll_create1(int flags)
epoll_create1 产生一个epoll 实例,返回的是实例的句柄epollfd。flag 可以设置为0 或者EPOLL_CLOEXEC,为0时函数表现与epoll_create一致,EPOLL_CLOEXEC标志与open 时的O_CLOEXEC 标志类似,即进程被替换时会关闭文件描述符。
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
(1)epfd:epoll 实例句柄;
(2)op:对文件描述符fd 的操作,主要有EPOLL_CTL_ADD、 EPOLL_CTL_DEL等;
(3)fd:需要操作的目标文件描述符;
(4)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、EPOLLOUT、EPOLLET、EPOLLLT等;一般data 共同体我们设置其成员fd即可,也就是epoll_ctl 函数的第三个参数。
3、int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
(1)epfd:epoll 实例句柄;
(2)events:结构体指针
(3)maxevents:事件的最大个数
(4)timeout:超时时间,设为-1表示永不超时
以上内容可参考:http://www.cppfans.org/1418.html
每个epollfd在内核中有一个对应的eventpoll结构对象。其中关键的成员是一个readylist(eventpoll:rdllist)和一棵红黑树(eventpoll:rbr)。一个fd被添加到epoll中之后(EPOLL_ADD),内核会为它生成一个对应的epitem结构对象。epitem被添加到eventpoll的红黑树中.红黑树的作用是使用者调用EPOLL_MOD的时候可以快速找到fd对应的epitem。调用epoll_wait的时候,将readylist中的epitem出列,将触发的事件拷贝到用户空间。之后判断epitem是否需要重新添加回readylist。epitem重新添加到readylist必须满足下列条件:
1) epitem上有用户关注的事件触发;
2) epitem被设置为水平触发模式(如果一个epitem被设置为边界触发则这个epitem不会被重新添加到readylist中)。
更为具体内容请参考:http://www.th7.cn/system/lin/201403/51432.shtml
回射服务器程序(附解释):
#include "unp.h"
#include <limits.h>
#include <vector>
#include <sys/epoll.h>
#include <iostream>
using namespace std;
typedef std::vector<struct epoll_event> EventList;
int main(int argc, char **argv){
int i,maxConn,listenfd, connfd, sockfd;
int epfd;
socklen_t cliLen;
char buffer[MAXLINE];
struct sockaddr_in seraddr,cliaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&seraddr, 0);
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
seraddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA *)&seraddr, sizeof(seraddr));
Listen(listenfd, LISTENQ);
struct epoll_event event;
event.data.fd = listenfd;
event.events = EPOLLIN | EPOLLET;
if((epfd = epoll_create1(EPOLL_CLOEXEC))<0)
err_quit("epoll_create1 error");
if(epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event)<0)
err_quit("epoll_ctl error");
EventList events(20);
std::vector<int> clients;
size_t eventSize = 0 ;
int readsize = 0, nready;
cliLen = sizeof(cliaddr);
for(;;){
eventSize = events.size() ;
nready = epoll_wait(epfd, &*events.begin(), eventSize, -1);
if(nready == -1){
if(errno==EINTR)
continue;
err_quit("epoll_wait error");
}
if(nready == 0)
continue;
if(nready == eventSize)
{
events.resize(2*eventSize);
}
for(int i = 0; i < nready; i++ ){
if(events[i].data.fd == listenfd){
connfd = Accept(listenfd, (SA *)&cliaddr, &cliLen);
clients.push_back(connfd);
event.data.fd = connfd;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &event);
}
else if(events[i].events & EPOLLIN){
sockfd = events[i].data.fd;
if((readsize = read(sockfd, buffer, MAXLINE))<0){
if(errno==ECONNRESET)
close(sockfd);
else
err_sys("read error");
}else if(readsize==0){
close(sockfd);
event = events[i];
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, &event);
clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end());
}else{
write(sockfd, buffer, readsize);
}
}
}
}
exit(0);
}
代码分析(结合epoll流程):
1~31行 与普通的套接字编程步骤相同。unp.h是《Unix网络编程中随书代码》,unp.h中包含了一些变量的定义和函数的声明在这里不必太在意
32行 epoll_create1()返回一个epollfd(在程序中取名为epfd),此时epollfd在内核中有一个对应的eventpoll结构对象。eventpoll中关键的成员是一个readylist (eventpoll:rdllist)和一棵红黑树(eventpoll:rbr)。
34行 epoll_ctl()添加listenfd到epoll中。listen有两个队列:一个是已完成连接(三路握手)队列,另一个是未完成连接队列。listenfd被添加epoll中之后(EPOLL_CTL_ADD),内核会为它生成一个对应的epitem结构对象。epitem被添加到eventpoll的红黑树中。
45行 第一次进入for循环执行45行的epoll_wait()函数时,由于是第一次进入循环epollfd(在程序中取名为epfd)所对应的eventpoll的红黑树中只有一个与listenfd对应的epitem结构的数据。也就是说此时,epoll_wait()函数是在等待监听套接字listenfd的队列中存在三路握手连接完成。因为epoll_wait()函数最有一个参数设置为-1,所以不存在epoll_wait()函数等待超时返回的情况。一旦listenfd的已连接不为空,相当于有I/O准备好,epoll_wait()函数一旦监测到便会立即返回。以上谈论的是第一次进入for循环的情况。第二次、第三次......以后epoll_wait()函数不仅要监测listenfd是否有新的三路握手完成,还要监测connfd已连接的客户端是否有I/O准备好。
61行 从61行开始以后的for循环用来处理已准备好的I/O。至于for循环中的if语句,如果某个epoll事件的fd是listenfd的话表明有新的客户连接,需要使用accept函数完成服务器与客户端的连接,并将connfd添加到epollfd中,此时内核为connfd为之生成一个对应的epitem结构对象,epitem被添加到eventpoll的红黑树中;如果某个epoll事件中的events标志为EPOLLIN,表明关联的fd可以进行读操作了,这时候服务器从套接字中读取数据并回射给客户端(这段代码是回射服务器程序)。
参考:http://www.th7.cn/system/lin/201403/51432.shtml
http://blog.youkuaiyun.com/vividonly/article/details/7539342