1. 程式人生 > >linux I/O複用之epoll

linux I/O複用之epoll

目錄

1、epoll簡介

2、epoll使用例子

3、epoll兩種模式

3.1、LT模式

3.2、ET模式

4、和select比較


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。