linux I/O複用之epoll
目錄
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。