Linux系统IO多路复用之epoll
epoll_create函数
int epoll_create(int size);
创建一个epoll的句柄,size指定需要监听描述符的最大数量。该函数返回一个fd,在使用完epoll之后需要调用close()关闭,否则可能导致fd耗尽。
epoll_ctl函数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll事件注册函数,参数解释:
- epfd:通过epoll_create函数获得的epoll句柄。
- op:操作的类型,有三种操作用三个宏表示:
宏 | 说明 |
---|---|
EPOLL_CTL_ADD | 注册新fd进入epfd中 |
EPOLL_CTL_MOD | 修改已经注册的fd的监听事件 |
EPOLL_CTL_DEL | 从epfd中删除一个fd |
- fd:要监听的文件描述符
- event:需要监听的事件,struct epoll_event结构如下:
typeof union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;
struct epoll_event{
__uint32_t events; /*监听的事件类型*/
epoll_data_t data; /*用户数据*/
}
events可以以下几个宏的集合:
宏 | 事件 |
---|---|
EPOLLIN | 文件描述符可读 |
EPOLLOUT | 文件描述符可写 |
EPOLLPRI | 文件描述符有紧急数据可读 |
EPOLLERR | 文件描述符发生错误 |
EPOLLHUP | 文件描述符被挂断 |
EPOLLET | 将EPOLL设为边缘触发 |
EPOLLONESHOT | 只监听一次事件,之后再需监听,需要再次注册 |
epoll_wait函数
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll_wait函数等待监视的事件发生,参数解释:
- epfd:通过epoll_create函数创建的epoll句柄。
- events:一个数组,内核监视到发生的事件集合。
- maxevents:events中元素数量最大值,不能超过epoll_create时的size。
- timeout:超时时间。单位为毫秒:0立即返回,-1永久阻塞。
epoll_wait函数返回值为需要处理事件的数目,如返回值为0则表示已超时。
epoll两种工作模式
LT:水平触发
- socket接收缓冲区中的数据字节数大于等于socket接收缓冲区低水位标记大小(一般为1),则会一直触发可读事件。
- socket发送缓冲区中可用空间字节数大于等于socket发送缓冲区低水位标记大小(一般为2048),并且该socket已连接或者不需连接(UDP套接字),则会一直触发可写事件。
ET:边缘触发
- socket的接收缓冲区状态发生变化时触发可读事件。注意,这里的状态变化指的是空的接收缓冲区接收了数据变为非空的缓冲区,而不包括缓冲区还有未处理数据的状态。
- socket的发送缓冲区状态发生变化时触发可写事件。注意,这里的状态变化指的的满的发送缓冲区发送了数据变为不满的缓冲区。
简单地说,水平触发会一直触发,这一点和select和poll相同,而边缘触发只会触发一次,这是epoll特有的。
若采用了ET模式,需要一直read/write直到出错为止。
使用ET模式可以便捷地处理EPOLLOUT事件,省去了打开与关闭EPOLLOUT的epoll_ctl函数调用。
epoll使用模板
int listen_sock=startup(8080);
struct sockaddr_in remote;
socklen_t len=sizeof(struct sockaddr_in);
int epfd=epoll_create(1024);
struct epoll_event events[1024],ev;
ev.data.fd=listen_sock;
ev.events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);
for(;;){
int nfds=epoll_wait(epfd,events,1024,-1);
for(int i=0;i<nfds;i++){
if(events[i].data.fd==listen_sock){//listen_sock上有可读数据
int client_sock=accept(listen_sock,(struct sockaddr*)&remote,&len);
ev.data.fd=client_sock;
ev.events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,client_sock,&ev);
}
else if(events[i].events&EPOLLIN){//有可读数据
char recv_buf[4096];
int client_sock=events[i].data.fd;
int ret=recv(client_sock,recv_buf,sizeof(recv_buf),MSG_DONTWAIT);
if(ret==0){
epoll_ctl(epfd,EPOLL_CTL_DEL,client_sock,&ev);
close(client_sock);
continue;
}
ev.data.fd=client_sock;
ev.events=EPOLLOUT;
epoll_ctl(epfd,EPOLL_CTL_MOD,client_sock,&ev);
}
else if(events[i].events&EPOLLOUT){//有可写数据并可写
int client_sock=events[i].data.fd;
int ret=send(client_sock,http_response_buf,strlen(http_response_buf),MSG_DONTWAIT);
if (ret<0){
epoll_ctl(epfd,EPOLL_CTL_DEL,client_sock,&ev);
close(client_sock);
continue;
}
ev.data.fd=client_sock;
ev.events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_MOD,client_sock,&ev);
}
}
}
close(listen_sock);
close(epfd);