I/O複用(三)---epoll機制

epoll實現機制分析:

epoll是Linux特有的I/O複用函數。它在實現和使用上與select和poll有很大的差異。首先,epoll使用一組函數來完成任務,而不是單個的函數。其次,epoll把用戶關心的文件描述符上的事件放在內核的一個時間表中,從而無需像select和epoll那樣每次調用都要重複傳入文件描述符和集或事件集。但epoll需要使用一個額外的文件描述符,來唯一標識內核中的這個事件表。而這個文件描述符使用epoll_create函數來創建。


size參數現在並不起作用,只是給內核一個提示。告訴它時間表需要多大。該函數返回的文件描述符將用作其他所有epoll系統調用的第一個參數,以指定要訪問的內核事件表。

     實質:epoll_create函數的返回值爲epfd,每個epfd在內核中有一個對應的eventpoll結構體對象。其中關鍵的成員是一個就緒隊列和一顆紅黑樹。一個fd被添加到epoll中之後,系統就會爲它生成一個對應的epitem結構對象。epitem被添加到eventpoll的紅黑樹中。紅黑樹的作用是使用者調用EPOLL_CTL_MOD、EPOLL_CTL_ADD或EPOLL_CTL_DEL的時候可以迅速找到對應的就緒事件。

epoll_ctl:


epfd參數是要操作的文件描述符,op參數則指定操作類型。event參數指定事件,它是epoll_event結構體指針類型。epoll_event的定義如下:


epoll_event中的events成員描述事件類型。epoll支持的事件類型和poll基本相同。data成員用於存儲用戶數據。

epoll_data是一個聯合體,其4個成員中使用最多的是fd和ptr。但他們不能同時使用。epoll_ctl成功時返回0,失敗時返回-1並設置errno。

epitem重新添加到readylist(就緒隊列)必須滿足下列條件。

1.epitem上有用戶關注的事件觸發。

2.epitem被設置爲水平觸發模式(如果一個epitem被設置爲邊界觸發則這個epitem不會被重新添加到readylist中)


epoll_wait:


epoll_wait是epoll系列系統調用的主要接口,它在一段超時時間內等待一組文件描述符上的事件。

該函數成功時返回就緒的文件描述符的個數,失敗時返回-1並設置errno。

原理:

epoll_wait函數如果檢測到時間,就將所有就緒的事件從內核事件表(由epfd參數指定)中複製到它的第二個參數events指向的數組中。這個數組只用於輸出epoll_wait檢測到的就緒事件,不想select和poll的參數是輸入輸出型參數,這樣就極大提高了應用程序索引就緒文件描述符的效率。

代碼實現:

/*************************************************************************
	> File Name: epoll_server.c
	> Author: ZX
	> Mail: [email protected] 
	> Created Time: Sat 18 Mar 2017 10:21:03 PM PDT
 ************************************************************************/

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<sys/epoll.h>
#include<stdlib.h>
#include<string.h>

#define _SIZE_ 1024

typedef struct epbuf
{
	int fd;
	char buf[_SIZE_];
}epbuf_t,*epbuf_p,**epbuf_pp;

static epbuf_p alloc_epbuf(int fd)
{
	epbuf_p ptr = (epbuf_p)malloc(sizeof(epbuf_t));
	if(ptr == NULL)
	{
		perror("malloc");
		exit(6);
	}
	ptr->fd = fd;
	printf("ptr->fd:%d\n",fd);
	return ptr;
}
static void delete_epbuf(epbuf_p ptr)
{
	if(ptr != NULL)
	{
		free(ptr);
	}
	ptr = NULL;
}

int startup(const char* _ip, int _port)
{
	assert(_ip);
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
	{
		perror("sock");
		exit(1);
	}

	int opt = 1;
	setsockopt(sock, SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt));

	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_addr.s_addr = inet_addr(_ip);
	local.sin_port = htons(_port);
	socklen_t len = sizeof(local);

	if(bind(sock, (struct sockaddr*)&local, len) < 0)
	{
		perror("bind");
		exit(2);
	}

	if(listen(sock, 5) < 0)
	{
		perror("listen");
		exit(3);
	}

	return sock;
}

int  main(int argc, char* argv[])
{
	if(argc != 3)
	{
		printf("Usage: %s [local_ip] [local_port]",argv[0]);
		return 4;
	}

	int epfd = epoll_create(256);
	if(epfd < 0)
	{
		perror("epoll_create");
		return 5;
	}
	int listen_sock = startup(argv[1], atoi(argv[2]));

	struct epoll_event _ev;
	_ev.events = EPOLLIN;
	_ev.data.ptr = alloc_epbuf(listen_sock);
//	_ev.data.fd = listen_sock;
	
	epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &_ev);

	struct epoll_event _evs[32];
	int max = 32;
	int timeout = 2000;
	int nums = 0;
	while(1)
	{
		switch(nums = epoll_wait(epfd, _evs, max, timeout))
		{
			case 0:
				printf("timeout...\n");
				break;
			case -1:
				perror("epoll_wait");
				break;
			default:
				{
					printf("nums: %d\n",nums);
					int i = 0;
					for(; i<nums; i++){
						sleep(1);
						int fd = ((epbuf_p)(_evs[i].data.ptr))->fd;
						printf("after fd: %d\n",fd);
						if(fd == listen_sock && (_evs[i].events & EPOLLIN)){
							struct sockaddr_in peer;
							socklen_t len = sizeof(peer);
							int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
							printf("after accept,new_sock:%d\n",new_sock);
							if(new_sock < 0){
								perror("accept");
								continue;
							}
							else if(new_sock > 0){
								printf("get a new client# port %d\n",ntohs(peer.sin_port));
								_ev.events = EPOLLIN;
								//_ev.data.fd = new_sock;
								_ev.data.ptr = alloc_epbuf(new_sock);
								epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &_ev);
							}
						}//if
						else if(fd != listen_sock && (_evs[i].events & EPOLLIN))
						{
							char* buf = ((epbuf_p)(_evs[i].data.ptr))->buf;
							printf("read  fd: %d\n",fd);
							ssize_t _s = read(fd, buf, _SIZE_-1);
							if(_s > 0){
								//succeed, change file descriptor
								buf[_s] = 0;
								printf("%s\n", buf);
								_ev.events = EPOLLOUT;
								epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &_ev);
							}
							else  if(_s == 0){
								printf("client is quit!\n");
								delete_epbuf(_evs[i].data.ptr);
								_evs[i].data.ptr = NULL;
								epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
								close(fd);
							}
							else{
								perror("read");
								continue;
							}
	
						}//else if   read file descriptor
						else if(fd != listen_sock && (_evs[i].events & EPOLLOUT))
						{
							printf("write:\n");
							const char* msg = "HTTP/1.0 200 OK \r\n\r\n<html><h1>HELLO WORLD!</h1></html>\n";
							write(fd, msg, strlen(msg));
							delete_epbuf(_evs[i].data.ptr);
							_evs[i].data.ptr = NULL;
							epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
							close(fd);
						}//else if   write file descriptor
					}//for
				}
				break;
		}//switch

	}//while
	return 0;
}






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