目录
1、epoll简介
epoll是2002年加入linux的I/O复用接口,是linux平台独有的,epoll不是采用轮询的方式,事件触发时采用回调函数的方式,所以效率比select要高,epoll主要有三个系统调用API
(1)int epoll_create(int size)
创建一个epoll句柄,linux2.6.8版本以后的不需要参数size,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll 后,必须调用close()关闭,否则可能导致fd被耗尽。
(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epoll的事件注册函数,epoll_ctl可以随时改变监听的事件,而不像select一样必须全部加载,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
struct epoll_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 :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的;
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
(3)int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
epoll_wait是阻塞的,返回所以已经触发的文件描述,采用回调的方式返回触发的文件描述,而不是轮询,所以效率高,超时返回0。
2、epoll使用例子
tcp_sever:
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<sys/unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<error.h>
#include<string.h>
int main(int argc, char *argv[]){
int server_fd = 0;
int port = 0;
char *ip = NULL;
int ret = 0;
struct sockaddr_in address;
char buf[512] = {0};
struct sockaddr_in client_address;
int socket_len = sizeof(client_address);
int client_fd = 0;
int epoll_fd = 0;
struct epoll_event events[100] = {0};
struct epoll_event event;
int i = 0;
int num = 0;
if(argc < 3){
printf("error argv!\n");
return -1;
}
port = atoi(argv[2]);
ip = argv[1];
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, &address.sin_addr);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if(server_fd < 0){
printf("socket error!\n");
return -1;
}
ret = bind(server_fd, (struct sockaddr*)&address, sizeof(address));
if(ret == -1){
printf("bind error!\n");
return -1;
}
ret = listen(server_fd, 5);
if(ret == -1){
printf("listen error!\n");
return -1;
}
epoll_fd = epoll_create(100);
if(epoll_fd == -1){
printf("epoll_create error!\n");
return -1;
}
event.data.fd = server_fd;
event.events = EPOLLIN ;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);
if(ret == -1){
printf("epoll_ctl error!\n");
return -1;
}
event.data.fd = STDIN_FILENO;
event.events = EPOLLIN;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
if(ret == -1){
printf("epoll_ctl error!\n");
return -1;
}
while(1){
num = epoll_wait(epoll_fd, events, 100, -1);
for(i = 0; i < num; i++){
if(events[i].data.fd == server_fd){
client_fd = accept(server_fd, (struct sockaddr*)&client_address, &socket_len);
if(client_fd < 0){
printf("accept error!\n");
}
printf("new client:%d\n", client_fd);
event.data.fd = client_fd;
event.events = EPOLLIN;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
if(ret == -1){
printf("epoll_ctl error!\n");
return -1;
}
}else if(events[i].data.fd == STDIN_FILENO){
bzero(buf, 512);
fgets(buf, 512, stdin);
ret = send(client_fd, buf, strlen(buf), 0);
}else if(events[i].events && EPOLLIN){
bzero(buf, 512);
ret = recv(client_fd, buf, sizeof(buf), 0);
if(ret == 0){
printf("client:%d is close\n", events[i].data.fd);
event.data.fd = events[i].data.fd;
event.events = EPOLL_CTL_DEL;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, &event);
if(ret == -1){
printf("epoll_ctl error!\n");
return -1;
}
continue;
}
printf("from client:%d recv:%s", events[i].data.fd, buf);
}else if(events[i].events && EPOLLHUP){
printf("from client:%d if bup\n", events[i].data.fd);
}
}
}
close(server_fd);
close(client_fd);
}
tcp_clietn:
#include<sys/socket.h>
#include<sys/epoll.h>
#include<sys/unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include <fcntl.h>
int main(int argc, char *argv[]){
int client_fd = 0;
int port = 0;
char *ip = NULL;
int ret = 0;
struct sockaddr_in server_addr;
int sock_len = sizeof(server_addr);
char buf[512] = {0};
struct epoll_event events[100] = {0};
struct epoll_event event;
int num = 0;
int i = 0;
int epoll_fd;
int flags;
if(argc < 3){
printf("argc error!\n");
return - 1;
}
port = atoi(argv[2]);
ip = argv[1];
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &server_addr.sin_addr);
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if(client_fd < 0){
printf("socket error!\n");
return -1;
}
ret = connect(client_fd, (struct sockaddr*)&server_addr, sock_len);
if(ret == -1){
printf("connect error!\n");
return -1;
}
flags = fcntl(client_fd, F_GETFL, 0);
fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
epoll_fd = epoll_create(100);
if(epoll_fd == -1){
printf("epoll create fail|\n");
}
event.data.fd = client_fd;
event.events = EPOLLIN ;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
if(ret == -1){
printf("epoll_ctl fail!\n");
return -1;
}
event.data.fd = STDIN_FILENO;
event.events = EPOLLIN;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
if(ret == -1){
printf("epoll_ctl fail!\n");
return -1;
}
while(1){
num = epoll_wait(epoll_fd, events, 100, -1);
printf("num:%d\n", num);
for(i = 0; i < num; i++){
if(events[i].data.fd == client_fd){
bzero(buf, 512);
ret = recv(client_fd, buf, sizeof(buf), 0);
if(ret < 0){
if(errno == EINTR){
printf("error == EINTR\n");
continue;
}
printf("read error!\n");
break;
}else if(ret == 0){
printf("client:%d is close\n", events[i].data.fd);
event.data.fd = events[i].data.fd;
event.events = EPOLL_CTL_DEL;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, &event);
if(ret == -1){
printf("epoll_ctl error!\n");
return -1;
}
printf("server is close!\n");
break;
}
printf("client rcv:%s\n", buf);
}else if(events[i].data.fd == STDIN_FILENO){
bzero(buf, 512);
fgets(buf, 512, stdin);
ret = send(client_fd, buf, strlen(buf), 0);
}
}
}
close(client_fd);
}
3、epoll两种模式
epoll有两种触发模式边沿触发和水平触发
3.1、LT模式
LT模式当文件描述有可读,如果用户没有读取完毕内核会一致触发通知进程文件描述可读,直到内容读取完毕。LT模式可以工作在阻塞模式、也可以工作在非阻塞模式。
3.2、ET模式
ET模式是当文件描述可读,内核会触发一次通知应用进程,如果用户没有全部读取完毕以后也不会再触发。所以ET模式文件描述必须设置为非阻塞模式,当实际读到的小于预期要读的就说明已经获取完毕,就可以退出了。
4、和select比较
(1)epoll对监听的文件描述符没有限制,而select最大支持1024个文件描述符。
(2)当一个文件描述符准备就绪内核采用callback回调的机制返回所以准备的文件描述符,而select采用是轮询遍历所有监听的文件描述符。
(3)当一个accept一个新的文件描述符就要调用epoll_ctl系统调用,每个文件描述符需要调用两次系统调用,而select只调用一次,所有如果是很多短连接select的效率会比epoll要和,但长连接建议使用epoll。