以下内容是参考华清远见《linux/unix系统编程手册》对epoll的一个个人总结,是我在华清远见比较全面的总结。 一、epoll的优点 同I/O多路复用和信号驱动I/O一样,linux的epoll API可以检查多个文件描述符上的I/O就绪状态。epoll API的主要优点 1.当有大量的文件描述符需要检查时,epoll的性能延展性比select()和epoll(高很多) 2.epoll API既支持水平触发也支持边缘触发,与之相反,select和poll只支持水平触发,而信号驱动I/O只支持边缘触发 3.可以避免复杂的信号处理流程(比如信号队列溢出时的处理) 4.灵活性高,可以指定我们希望检查的时间类型 二、epoll系统调用组成 1.epoll_create()创建一个epoll实例,返回代表该实例的文件描述符 2.epoll_ctl()操作同epoll实例相关联的兴趣列表 3.epoll_wait()返回与epoll实例相关联的就绪列表中的成员 #include
int epoll_create(int size); 功能:创建一个新的epoll实例,其对应的兴趣列表初始化为空 参数:size 想要检查文件描述符的个数(linux2.6.8之后该参数不再使用) 返回值:成功文件描述符,失败-1 #include int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 功能:修改epoll的兴趣列表 参数:epfd epoll_create的返回值 fd 要修改的文件描述符(可以是无名管道、有名管道、套接字、消息队列、终端、设备等,但是不能是普通文件或目录的文件描述符) op: EPOLL_CTL_ADD 添加fd到兴趣列表 EPOLL_CTL_MOD 修改已经注册的fd的监听事件; EPOLL_CTL_DEL 从epfd中删除一个fd 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 */ }; 常用的事件类型: EPOLLIN :表示对应的文件描述符可以读; EPOLLOUT:表示对应的文件描述符可以写; EPOLLPRI:表示对应的文件描述符有紧急的数据可读 EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断; EPOLLET: 表示对应的文件描述符有事件发生; 返回值:成功0 失败-1 #include
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 功能:返回epoll实例中处于就绪态的文件描述符信息,单个epoll_wait()调用能返回多个就绪态文件描述符的信息。 参数:epfd epoll实例 events 存放文件描述符的信息 maxevents 文件描述符的大个数 timeout -1 阻塞等待 0 非阻塞 >0 阻塞的大时间 返回值:成功0 失败-1
三、网络中的应用实例 1.net.h #ifndef _NET_H_ #define _NET_H_
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include
#include #define MAX_EVENTS 500
#endif 2.server.c #include "head.h"
int sockfd, acceptfd,listenfd; struct sockaddr_in serveraddr, clientaddr; socklen_t len = sizeof(serveraddr); struct epoll_event ev, events[MAX_EVENTS]; int rv,i; int epollfd,fds;
int tcp_create_socket(void) { //1、设定基于TCP通信的标准 sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0) { perror("fail to socket"); return -1; }
bzero(&serveraddr, len); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(atoi("8000"));//端口号 serveraddr.sin_addr.s_addr = inet_addr("192.168.8.199"); //IP地址
//2、绑定IP地址和端口号 if(bind(sockfd, (struct sockaddr*)&serveraddr, len) < 0) { perror("fail to bind"); close(sockfd); return -1; }
//3、设置监听描述符的个数 if(listen(sockfd, 5) < 0) { perror("fail to listen"); close(sockfd); return -1; }
return sockfd; }
int mz_process_data(int acceptfd)//数据处理 { int bytes = 0; char buf[100];
bytes = recv(acceptfd, buf, 100, 0); printf("----------------n"); if(bytes < 0) { perror("recv error"); return -1; }
if(bytes == 0) //说明客户端放弃链接 { return -2; }
printf("client:%sn", buf);
return 0;
}
int main(int argc, const char *argv[]) { epollfd = epoll_create(MAX_EVENTS);//创建对象 if(epollfd < 0) { perror("fail to epoll!"); return -1; }
listenfd = tcp_create_socket();
fcntl(listenfd,F_SETFL, O_NONBLOCK);//设置非阻塞
ev.data.fd = listenfd; ev.events = EPOLLIN; rv = epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);//加入监听事件 if (rv < 0) { perror("epoll_ctl err:"); return -1; }
while(1) { fds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if(fds < 0) { perror("epoll_wait error"); return -1; } //轮寻被激活的套接子 for(i = 0; i < fds; i++) { if(events.data.fd == listenfd) //判断是否是监听的对象(描述符) { acceptfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len); if(acceptfd < 0) { perror("accept error"); continue; }
ev.data.fd = acceptfd;
ev.events = EPOLLIN | EPOLLET; //将链接的客户端添加到关注的事件中去 epoll_ctl(epollfd, EPOLL_CTL_ADD, acceptfd, &ev); continue; } else { rv = mz_process_data(events.data.fd); if(rv == -2) //客户端放弃链接,将描述符从事件中清除 { epoll_ctl(epollfd, EPOLL_CTL_DEL, events.data.fd, &ev); close(events.data.fd); continue; } } } }
return 0; } 3.client.c #include "head.h"
#define N 32
int main(int argc, const char *argv[]) { int sockfd; struct sockaddr_in serveraddr; socklen_t len = sizeof(serveraddr); char buf[N] = {0}; //1、设定基于TCP通信的标准 sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0) { perror("fail to socket"); return -1; }
bzero(&serveraddr, len); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(atoi("8000"));//端口号 serveraddr.sin_addr.s_addr = inet_addr("192.168.8.199"); //IP地址
//2、绑定IP地址和端口号 if(connect(sockfd, (struct sockaddr*)&serveraddr, len) < 0) { perror("fail to connect"); close(sockfd); return -1; } //客户端用来发送数据 while(1) { fgets(buf, N, stdin); buf[strlen(buf) - 1] = ' |