歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

【Linux】多路復用之—epoll

一、多路復用之——epoll

int

epoll_create(int size);

int

epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);

int

epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);

1、epoll_create:創建一個epoll句柄,size參數可以忽略

當創建一個句柄後,會占用一個fd值,故在使用完後,要close(fd)。

2、epoll_ctl:注冊要監聽的事件類型

參數:(1)epfd:epoll_create的返回值;

(2)op:EPOLL_CTL_ADD

注冊新fd到epfd中

EPOLL_CTL_MOD

修改已經注冊的fd

EPOLL_CTL_DEL 從epfd中刪除一個fd

(3)fd:要監聽的fd;

(4)event:監聽的事件;

struct epoll_event{

_uint32_t events;

epoll_data_t data;

}

typedef union epoll_data{

void* ptr;

int fd;

_uint32_t U32;

_uint64_t U64;

}

(5)

events集合:

EPOLLIN:表示對應的文件描述符可以讀;

EPOLLOUT:表示對應的文件描述符可以寫;

EPOLLPRI:表示對應的文件描述符有緊急事件可讀;

EPOLLERR:表示對應的文件描述符發生錯誤;

EPOLLHUP:表示對應的文件描述符被掛斷;

EPOLLET:將EPOLL設為邊緣觸發模式;

EPOLLLT:將EPOLL設為水平觸發模式;

EPOLLONESHOT:只監聽一次事件;

3、epoll_wait:收集在epoll監控的事件中已經發生的事件

參數:(1)epfd:創建的epoll句柄;

(2)events:已分配好的epoll_events結構體,epoll會將發生的事件放到events中;

(3)maxevents:events的大小;

(4)timeout:NULL:沒有timeout,一直阻塞等待知道某個事件發生;

0:僅檢測描述符集合的狀態,然後立即返回;

特定值:等待timeout時間,如果沒有發生,超時返回。

4、epoll的兩種工作模式:

epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由於一個文件句柄的阻塞讀、阻塞寫操作把處理多個文件描述符的任務餓死。

epoll工作在LT模式的時候,在收到多個數據的時候仍然會產生多個事件,支持阻塞和非阻塞接口,這樣,內核告訴你一個文件描述符是否就緒了,然後你可以進行I/O,若你不做任何操作,內核還是會繼續通知你的,錯誤率小。

ET與LT的區別在於,當一個新事件到來時,ET模式下當然可以從epoll_wait調用中獲取到這個事件,可是如果這次沒有把這個事件對應的套接字緩沖區處理完,在這個套接字中沒有新事件到來時,ET模式下無法再次獲取數據。但LT正好相反,只要一個事件對應的套接字緩沖區還有數據,就能夠獲取。

5、epoll模型的優點:

(1)使用內存映射(mmap)技術,避免用戶到內存的拷貝;

(2)epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait時使得到通知;

(3)監視的文件描述符數量不受限制,它所支持的fd上限是最大可以打開文件的數目;

(4)I/O的效率不會隨著fd數量的增加而下降,select、poll實現需要自己不斷輪詢所有fd集合,指到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll_wait不斷輪詢就緒鏈表,期間也可能睡眠和喚醒多次交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,但select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只需要遍歷就緒鏈表是否為空就行了。這節省了大量的CPU時間。這就是回調機制的性能提升。

代碼示例:

epoll_server.c

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<fcntl.h>
#define MAX_NUM 64
int startup(int port)
{
	int sock=socket(AF_INET,SOCK_STREAM,0);
	if(sock<0){
		perror("socket");
		exit(1);
	}
	struct sockaddr_in local;
	local.sin_family=AF_INET;
	local.sin_port=htons(port);
	local.sin_addr.s_addr=htonl(INADDR_ANY);
	if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
		perror("bind");
		exit(2);
	}
	if(listen(sock,5)<0){
		perror("listen");
		exit(3);
	}
	return sock;
}
//set file descriptor for no blocking mode
void set_nonoblock(int _fd)
{
	int fl=fcntl(_fd,F_GETFL);
	if(fl<0){
		perror("fcntl");
		return;
	}
	fcntl(_fd,fl | O_NONBLOCK);
	return;
}
//recv data
int read_data(int _fd,char* buf,int len)
{
	ssize_t _size=-1;
	int total=0;
	while((_size=recv(_fd,buf+total,len-1,MSG_DONTWAIT))){
		if(_size>0){
			total+=_size;
		}else if(_size==0){//file end
			return 0;
		}else{
			return -1;
		}
	}
}
int main()
{
	short port=8080;
	int listen_sock=startup(port);
	struct sockaddr_in client;
	socklen_t len=sizeof(client);
	int epoll_fd=epoll_create(256);//create epoll handle
	int timeout=1000;
	if(epoll_fd<0){
		perror("epoll_create");
		exit(1);
	}
	struct epoll_event _ev;//save care fd
	_ev.events=EPOLLIN;
	_ev.data.fd=listen_sock;
	if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&_ev)<0){
		perror("epoll_ctl");
		goto LABLE;
	}
	struct epoll_event _ev_out[MAX_NUM];//save ready fd
	char buf[1024*5];
	memset(buf,'\0',sizeof(buf));
	int ready_num=-1;//save ready_fd num
	while(1){
		switch(ready_num=epoll_wait(epoll_fd,_ev_out,MAX_NUM,timeout)){
			case 0:
				printf("timeout\n");
				break;
			case -1:
				perror("epoll_wait");
				break;
			default:
				{
					int i=0;
					for(;i<ready_num;i++){
						int _fd=_ev_out[i].data.fd;
						//listen_sock
						if(_fd==listen_sock && (_ev_out[i].events & EPOLLIN)){
							int new_sock=accept(_fd,(struct sockaddr*)&client,&len);
							if(new_sock<0){
								perror("accept");
								continue;
							}
							printf("get a new connect...\n");
							set_nonoblock(new_sock);
							_ev.events=EPOLLIN | EPOLLET;
							_ev.data.fd=new_sock;
							if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&_ev)<0){
								perror("epoll_ctl");
								close(new_sock);
								continue;
							}
							continue;
						}
						//data_sock
						if(_ev_out[i].events & EPOLLIN){
							if(read_data(_fd,buf,sizeof(buf))==0){
								printf("client close...");
								epoll_ctl(epoll_fd,EPOLL_CTL_DEL,_fd,NULL);
							}
							printf("%s\n",buf);
						}
					}
				}
				break;
		}
	}
LABLE:
	close(epoll_fd);
	return 0;
}
Makefile:

epoll_server:epoll_server.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -rf epoll_server

Copyright © Linux教程網 All Rights Reserved