<1> 内核事件表
epoll是Linux特有的I/O复用函数,在实现和使用上与poll和selec有所不同。
- 首先,epoll使用一组函数来完成任务,而非单个函数
- epoll把用户关心的文件描述符上的事件放在内核的一个事件表中,从而无需像select和poll那样每次调用都要从新传入描述符集或事件集。
1) epoll_create
创建一个额外的文件描述符,来唯一标识内核的事件表。
#include <sys/epoll.h>
int epoll_create(int size);
// 返回值作为其它所有epoll_*函数的第一个参数,用以指明要访问的内核事件表。
// size参数现在并不起作用,只是给内核一个提示,事件表可能需要多大
2) epoll_ctl
用来操作内核事件表。
#include <sys/epoll.h>
int epoll_ctl(int epollfd, int op, struct epoll_event* event);
// epollfd 调用epoll_create()函数返回的内核事件表描述符
// fd 要操作的文件的描述符
// op 操作类型(EPOLL_CTL_ADD(添加)、 EPOLL_CTL_MOD(修改)、 EPOLL_CTL_DEL(删除))
// event 指定参数事件
// 调用成功返回 0 失败返回 -1
<2> epoll_wait函数
在一段超时时间内,等待一组文件描述符上的事件
int epoll_wait(int epollfd, struct epoll_event* events, int maxevents, int timeout);
// maxevents 表示最多可监听数
// 函数的执行过程:
// epoll_wait()函数若监听到了事件,就将所有就绪的事件从内核事件表(由epollfd指定)中复制到events指向的数组中,此数组只用作输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既要用于用户注册,又要用于输出内核检测到的就绪事件
<3> LT 与 ET 模式
(epoll对文件描述符的操作模式,LT(电平触发)、ET(边沿触发))。
- LT 模式是默认的工作模式,此模式下,epoll相当于一个效率较高的poll
- ET 模式: 当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符。
两者的对比在于:
对于一个文件描述符,当采用LT模式工作时,当epoll_wait检测到其上有事件发生时并将此事件通知应用程序之后,应用程序可以不立即处理,那么当程序下一次调用 epoll_wait时,epoll_wait会再次向应用程序通知此事。但当使用ET模式时,应用程序必须立即处理,因为后续epoll_wait调用将不会再次通知此事件 。
<4> 例子: 使用ET/LT模式接收数据
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/epoll.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10
int setNonblocking(int fd){//设置文件描述符为非阻塞
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLCOK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
void addfd(int epollfd, int fd, bool enable_et){
//向内核时间表添加感兴趣的事件
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;
if(enable_et){
event.events |= EPOLLET;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setNonblocking(fd);
}
void LT(int epollfd, int listenfd, int num, epoll_event* events){
char buf[BUFFER_SIZE];
for(itn i = 0; i < num; i++){
int sockfd = events[i].data.fd;
if(sockfd == listenfd){
struct sockaddr_in client;
socklen_t client_len = sizeof(client);
int connfd = accept(listenfd, (struct sockaddr*)&client, &client_len);
addfd(epollfd, fd, false);
}else if(events[i].events & EPOLLIN){
//由于使用的时LT模式,所以即使在此处只读取BUFFER_SIZE-1个字节的内容,由于会重复通知,我们最终也一样可以读取完所有内容
memset(buf, '\0', BUFFER_SIZE);
int ret = recv(fd, buf, BUFFER_SIZE-1, 0);
if(ret <= 0){
close(fd);
break;
}
printf("get %d bytes datas from fd %d: %s\n", ret, sockfd, buf);
}else{
printf("something else happend.\n");
}
}
}
void ET(int epollfd, int listenfd, int num, epoll_event* events){
char buf[BUFFER_SIZE];
for(int i = 0; i < num; i++){
int sockfd = events[i].data.fd;
if(sockfd == listenfd){
struct sockaddr_in client;
socklen_t client_len;
int connfd = accept(listenfd, (struct sockaddr*)&client, &client_len);
addrfd(epollfd, fd, true);
}else if(event[i].events & EPOLLIN){
//使用ET模式,必须一次将所有的数据全部读出
while(1){
memset(buf,'\0', BUFFER_SIZE);
int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
if(ret < 0){
if(errno == EAGAIN || errno == EWOULDBLOCK){
printf("read later!\n");
break;
}
close(sockfd);
break;
}else if(ret == 0){
close(sockfd);
}else{
printf("get %d bytes data from fd %d: %s\n", ret, sockfd, buf);
}
}//end while
}else{
printf("something else happend.\n");
}
}
}
int main(int argc, char *argv[])
{
if(argc != 3){
printf("Usage: %s IP_ADDRESS PORT_NUMBER\n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in address;
bzero(address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.port = htons(port);
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
int ret = bind(sockfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);
epoll_event event[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
assert(epollfd != -1);
addfd(epollfd, listenfd, true);
while(1){
ret = epoll_wait(epollfd, event, MAX_EVENT_NUMBER, -1);
if(ret < 0){
printf("epoll failure.\n");
break;
}
LT(epollfd, listenfd, ret, &event);
//ET(epollfd, listenfd, ret, &event);
}
close(sockfd);
return 0;
}