epoll詳解(Linux代碼實現)

epoll是一個Linux內核 的系統調用,一個可擴展的I / O事件通知機制。它的功能是監控多個文件描述符,看看I / O有可能在其中任何一個。它是爲了取代舊的POSIX select和系統調用,實現了更爲苛刻的應用更好的性能,在觀看了數文件描述符較大(不同於舊的系統調用,這在操作的時間O(N),工作在O(1)的時間 )。類似的FreeBSD的,因爲它操作的配置的內核對象上,暴露於用戶空間作爲其自身的一個文件描述符。





一個簡單的epoll代碼如下。該程序和select的例子一樣,故不在多介紹,讀者可詳細看看

<span style="font-size:18px;">#include<stdio.h>
#include<sys/socket.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<sys/time.h>
#include<sys/epoll.h>

static void guse(const char* prac)
{
printf("%s [ip] [port]..\n",prac);
}

static int my_select(const char* _ip, const int _port){    //獲得socket套接字
	int sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0){
		perror("create socket error...");
		exit(1);
	}
	
	struct sockaddr_in server_socket;
	bzero(&server_socket,sizeof(server_socket));
	server_socket.sin_family = AF_INET;
	server_socket.sin_addr.s_addr =inet_addr(_ip);
	server_socket.sin_port = htons(_port);
	
	int opt = 1;
	setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
	
	if(bind(sock,(struct sockaddr*)&server_socket,sizeof(server_socket))< 0)
	{
		perror("bind error ...");
		exit(2);
	}
	
	if(listen(sock,5) < 0 ){
		perror("listen errno ...");
		close(sock);
		 exit(3);
	}

	return sock;
}




int main(int argc,char* argv[])
{
   if(argc != 3)
 {
   guse(argv[0]);
     exit(0);
 }
   int listen_sock =  my_select(argv[1], atoi(argv[2]));
   int fdep = epoll_create(256);   //創建epoll
   if(fdep < 0){
	   perror("epoll_creact...\n");
	   exit(4);
   }

    struct epoll_event _ev,revent[20];
	_ev.events = EPOLLIN;
	_ev.data.fd = listen_sock;
	if(epoll_ctl(fdep,EPOLL_CTL_ADD,listen_sock,&_ev)< 0){  //配置註冊函數
		perror("epoll _ctl is errno ..\n");
		exit(5);
	}

	int times = -1;
	int fdnum;

   while(1){
	   int revent_size = sizeof(revent)/sizeof(revent);
		switch(fdnum = epoll_wait(fdep,revent,revent_size,times)){  //收集等待事件
			case -1:
				printf("selsect errno..\n");
				exit(6);
				break;
			case 0:
				printf("Time out \n");
				break;
			default:
				{
					int index = 0;
					int new_fd = 0;
					struct sockaddr_in cli;
					socklen_t len = sizeof(cli);
					char buf[1024];

					for(;index < fdnum; index++){
					int fd = revent[index].data.fd;
						if(listen_sock == fd && revent[index].events & EPOLLIN){  
							new_fd =accept(listen_sock,(struct sockaddr*)&cli,&len);	
						if(new_fd >0 ){   //接受了一個新的鏈接
							printf("%s:%d\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
				 	_ev.data.fd = new_fd;
					_ev.events = EPOLLIN;
					if(epoll_ctl(fdep,EPOLL_CTL_ADD,new_fd,&_ev) <0 ){  //再把這個鏈接增加到epoll
					perror("accept error...\n");
				    close(fd);
			     	exit(7);
						}
				}
				}else{
				if(revent[index].events & EPOLLIN){   //如果是普通的,便輸出
					memset(buf,'\0',sizeof(buf));
				ssize_t _s = recv(fd,buf,sizeof(buf)-1,0);
				if(_s == 0){
					printf("client close ...\n");
					epoll_ctl(fdep,EPOLL_CTL_DEL,fd,NULL);
					close(fd);
				}
				else if(_s < 0)
				{
					printf("errno  read..\n");
				}
				else{
					printf("client:%s",buf);
				}
				}
				}
	}
					}
				}
				break;
		}
   }

return 0;
}</span>

epoll同時提供了邊沿觸發和電平觸發模式。在邊沿觸發模式,調用epoll_wait將返回只有當一個新的事件排隊的epoll對象,而在電平觸發模式,epoll_wait將只要條件成立返回。

例如,如果一個管到與登記的epoll,已接收到數據,則調用epoll_wait將返回,信令數據的存在被讀取。假設讀者僅消耗來自緩衝器的數據的一部分。在電平觸發模式下,還呼籲epoll_wait將立即返回,只要管道緩衝區包含要讀取的數據。在邊沿觸發模式,但是,epoll_wait將只一次新數據寫入到管道返回。

下面便是給ET模式的代碼,只是在上面代碼的情況下做出了更改:

<span style="font-size:18px;">#include<stdio.h>
#include<sys/socket.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<sys/time.h>
#include<sys/epoll.h>
#include <fcntl.h>

static void guse(const char* prac)
{
printf("%s [ip] [port]..\n",prac);
}

static int read_data(int fd, char buf[]){   //對讀的封裝,考慮考ET的特性,故而,要一次將數據讀完
    int nread = 0;
    int n = 0;
    while(nread = (recv(fd,buf,128,0)) > 0){
        n = nread;
    }

    return nread;
}

static int set_noblock(int sock){       //使其阻塞的等待
    int fl = fcntl(sock,F_GETFL);
    return fcntl(sock,F_SETFL,fl|O_NONBLOCK);
}


static int my_select(const char* _ip, const int _port){
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0){
        perror("create socket error...");
        exit(1);
    }
    
    struct sockaddr_in server_socket;
    bzero(&server_socket,sizeof(server_socket));
    server_socket.sin_family = AF_INET;
    server_socket.sin_addr.s_addr =inet_addr(_ip);
    server_socket.sin_port = htons(_port);
    
    int opt = 1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    
    if(bind(sock,(struct sockaddr*)&server_socket,sizeof(server_socket))< 0)
    {
        perror("bind error ...");
        exit(2);
    }
    
    if(listen(sock,5) < 0 ){
        perror("listen errno ...");
        close(sock);
         exit(3);
    }

    return sock;
}




int main(int argc,char* argv[])
{
   if(argc != 3)
 {
   guse(argv[0]);
     exit(0);
 }
   int listen_sock =  my_select(argv[1], atoi(argv[2]));
   int fdep = epoll_create(256);
   if(fdep < 0){
       perror("epoll_creact...\n");
       exit(4);
   }

    struct epoll_event _ev,revent[128];
    _ev.events = EPOLLIN;
    _ev.data.fd = listen_sock;
    if(epoll_ctl(fdep,EPOLL_CTL_ADD,listen_sock,&_ev)< 0){
        perror("epoll _ctl is errno ..\n");
        exit(5);
    }

       int revent_size = sizeof(revent)/sizeof(revent);
    int times = -1;
    int fdnum;

   while(1){
        switch(fdnum = epoll_wait(fdep,revent,revent_size,times)){
            case -1:
                printf("epoll  errno..\n");
                exit(6);
                break;
            case 0:
                printf("Time out \n");
                break;
            default:
                {
                    int index = 0;
                    struct sockaddr_in cli;
                    socklen_t len = sizeof(cli);
                    char buf[1024];

                    for(;index < fdnum; index++){
                    int fd = revent[index].data.fd;
                        if(listen_sock == fd && revent[index].events & EPOLLIN){
                        int    new_fd =accept(listen_sock,(struct sockaddr*)&cli,&len);    
                        if(new_fd >0 ){
                            printf("%s:%d\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
                     _ev.data.fd = new_fd;
                    _ev.events = EPOLLIN | EPOLLET;

                    set_noblock(new_fd);
                    epoll_ctl(fdep,EPOLL_CTL_ADD,new_fd,&_ev);
                }
                    }else{
                if(revent[index].events & EPOLLIN){
                    memset(buf,'\0',sizeof(buf));
                int ret = read_data(fd,buf);
                if(ret < 0){
                    printf("###############\n");
                    printf("client close ...\n");
                }
                else if(ret == 0)
                {
                    printf("client close ..\n");
                }
                else{
                    printf("recv ...\n");
                }
                }
                _ev.data.fd = fd;
                _ev.events = revent[index].events | EPOLLOUT;

                if(epoll_ctl(fdep,EPOLL_CTL_MOD,fd,&_ev)<0){
                    perror("MOD ...\n");
                }

                 if(revent[index].events & EPOLLOUT){
                    const char *msg = "HTTP/1.1 200 OK\r\n\r\n<h1>hello world  0.0</h1>\r\n";
                    send(fd,msg,strlen(msg),0);
                    epoll_ctl(fdep ,EPOLL_CTL_DEL,fd,NULL);
                    close(fd);
                }
            }
        }

                break;
        }
   }
 }

return 0;
}


}</span>



接下來,我們用瀏覽器看看:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章