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。

猜你喜欢

转载自blog.csdn.net/City_of_skey/article/details/85226618