1. 程式人生 > >I/O函式複用 -- epoll

I/O函式複用 -- epoll

核心事件表

epoll函式是linux特有的I/O複用函式。它再在實現和使用上與select、poll有一下差異:

  1. epoll使用一組函式來完成任務,而不是單個函式
  2. epoll把使用者關心的檔案描述符上的事件放在核心的一個事件表中,從而無需像select和poll那樣每次呼叫都要重複傳入檔案描述符集或事件集。

epoll需要使用一個額外的檔案描述符,來唯一標識核心中的這個事件表。這個檔案描述符使用epoll_create函式來建立:

# include<sys/epoll.h>
int epoll_create(int size);
  • 下面的函式用來操作epoll的核心事件表:
# incldue<sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
fd引數是要操作的檔案描述符,op引數則指定操作型別,有以下3種:
1、EPOLL_CTL_ADD,往時間表中註冊fd上的事件
2、EPOLL_CTL_MOD,修改fd上的註冊事件
3、EPOLL_CTL_DEL,刪除fd上的註冊事件
event引數指定事件,他是epoll_event結構指標型別。定義如下:
struct epoll_event
{
    _uint32_t events;//epoll事件
    epoll_data_t data;//使用者資料
};
其中events成員描述事件型別。epoll支援的事件型別和poll基本相同。表示epoll事件型別的巨集是在poll對應的巨集前加上E,比如epoll的資料可讀事件是EPOLLIN,但epoll有兩個額外的事件型別——EPOLLET和EPOLLONESHOT。它們對於epoll的高效運作非常關鍵。data成員用於儲存使用者資料,其型別epoll_data_t的定義如下:
typedef union epoll_data
{
    vpod *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64
}epoll_data_t;

epoll_data_t是一個聯合體,其4個成員中使用最多的是fd,它指定事件所屬的目標檔案描述符。ptr成員可用來指定與fd相關的使用者函式。但由於epoll_data_t是一個聯合體,我們不能同時使用ptr成員和fd成員,因此,如果要將檔案描述符和使用者資料關聯起來,以實現快速的資料訪問,只能使用其他手段,比如放棄使用fd成員,而在ptr指向的使用者資料中包含fd
epoll_ctl成功時返回0,失敗時返回-1,並設定errno

epoll_wait函式

epoll系列系統呼叫的主要介面是epoll_wait函式。它在一段超時時間內等待一組檔案描述符上的事件,其原型如下:

# include<sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

        該函式成功時返回就緒的檔案描述符的個數,失敗時返回-1並設定errno;timeout引數是超時時間。maxevents引數指定最多監聽多少個事件,它必須大於0。

       epoll_wait函式如果檢測到事件,就將所有就緒的事件從核心事件表中複製到它的第二個引數events指向的陣列中。這個陣列只用於輸出wpoll_wait檢測到的就緒事件,而不向select和poll的陣列引數那樣即用於傳入使用者註冊的事件,又用於輸出核心檢測到的就緒事件。這就極大地提高了應用程式索引就緒檔案描述符的效率。

LT和ET模式

       epoll對檔案描述符的操作有兩種模式:LT(電平觸發)和ET(邊沿觸發)模式。LT模式是預設的工作模式,這種模式下epoll相當於一個效率較高的poll。當往epoll核心事件表中註冊一個檔案描述符上的EPOLLET事件時,epoll將以ET模式來操作該檔案描述符,ET模式是epoll的高效工作模式。

       對於採用LT工作模式的檔案描述符,當epoll_wait檢測到其上有事件發生並將此事件通知應用程式後,應用程式可以不立即處理該事件。這樣,當應用程式下一次呼叫epoll_wait時,epoll_wait還會再次嚮應用程式通告此事件,直到該事件被處理。

      而對於採用ET工作模式的檔案描述符,當epoll_wait檢測到其上有事件發生並將此事件通知應用程式後,應用程式必須立即處理該事件,因為後續的epoll_wait呼叫將不再向應用程式通知這一事件。可見ET模式在很大程度上降低了同一個epoll事件被重複呼叫觸發的次數,因此效率要比LT模式高。

程式碼清單

  • LT模式下的伺服器:
# include<stdio.h>
# include<sys/epoll.h>
# include<sys/socket.h>
# include<assert.h>
# include<arpa/inet.h>
# include<string.h>
# include<unistd.h>
# include<sys/socket.h>
# include<stdlib.h>
# include<error.h>
#define MAXFD 10
void epoll_add(int epfd, int fd)
{
	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.fd = fd;

	if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1)
	{
		perror("epoll ctl error");
	}
}
void epoll_del(int epfd, int fd)
{
	if(epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1)
	{
		perror("epoll ctl del error\n");
	}
}
int main()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(sockfd != -1);

	struct sockaddr_in saddr, caddr;
	memset(&saddr, 0, sizeof(saddr));

	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
	assert(res != -1);

	listen(sockfd, 5);

	int epfd = epoll_create(MAXFD);
	epoll_add(epfd, sockfd);
	struct epoll_event events[MAXFD];

	while(1)
	{
		int n = epoll_wait(epfd, events, MAXFD, 5000);
		if(n == -1)
		{
			printf("epoll_wait error\n");
			continue;
		}
		else if(n == 0)
		{
			printf("time out\n");
			continue;
		}
		else
		{
			int i = 0;
			for(; i < n; i++)
			{
				int fd = events[i].data.fd;
				if(events[i].events & EPOLLIN)
				{
					if(fd == sockfd)
					{
						int len = sizeof(caddr);
						int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
						if( c < 0)
						{
							continue;
						}
						printf("accept c = %d\n", c);
						epoll_add(epfd, c);
					}
					else
					{
						char buff[128] = {0};
						int num = recv(fd, buff, 127, 0);
						if(num <= 0)
						{
							epoll_del(epfd, fd);
							close(fd);
							printf("one client over\n");
							continue;
						}
						else
						{
							printf("recv(%d) = %s\n",fd, buff);
							send(fd, "ok", 2, 0);
						}
					}
				}

			}
		}
	}
}
  • ET模式下的伺服器:
# include<stdio.h>
# include<sys/epoll.h>
# include<sys/socket.h>
# include<assert.h>
# include<arpa/inet.h>
# include<string.h>
# include<unistd.h>
# include<sys/socket.h>
# include<stdlib.h>
# include<errno.h>
# include<fcntl.h>
#define MAXFD 10

void setnonblock(int fd)
{
	int oldfl = fcntl(fd, F_GETFL);
	int newfl = oldfl | O_NONBLOCK;
	if(fcntl(fd, F_SETFL, newfl) == -1)
	{
		perror("fcntl error\n");
	}
}
void epoll_add(int epfd, int fd)
{
	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.fd = fd;

	if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1)
	{
		perror("epoll ctl error");
	}
	setnonblock(fd);
}
void epoll_del(int epfd, int fd)
{
	if(epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1)
	{
		perror("epoll ctl del error\n");
	}
}
int main()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(sockfd != -1);

	struct sockaddr_in saddr, caddr;
	memset(&saddr, 0, sizeof(saddr));

	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
	assert(res != -1);

	listen(sockfd, 5);

	int epfd = epoll_create(MAXFD);
	epoll_add(epfd, sockfd);
	struct epoll_event events[MAXFD];

	while(1)
	{
		printf("epoll_wait\n");
		int n = epoll_wait(epfd, events, MAXFD, 5000);
		if(n == -1)
		{
			printf("epoll_wait error\n");
			continue;
		}
		else if(n == 0)
		{
			printf("time out\n");
			continue;
		}
		else
		{
			int i = 0;
			for(; i < n; i++)
			{
				int fd = events[i].data.fd;
				if(events[i].events & EPOLLIN)
				{
					if(fd == sockfd)
					{
						int len = sizeof(caddr);
						int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
						if( c < 0)
						{
							continue;
						}
						printf("accept c = %d\n", c);
						epoll_add(epfd, c);
					}
					else
					{
						while(1)
						{
							char buff[128] = {0};
							int num = recv(fd, buff, 1, 0);
							if(num == -1)
							{
								if(errno == EAGAIN || errno == EWOULDBLOCK)
								{
									send(fd, "ok", 2, 0);
								}
								break;
							}
							else if(num == 0)
							{
								epoll_del(epfd, fd);
								close(fd);
								printf("one client over\n");
								break;
							}
							printf("recv %d = %s\n",fd, buff);
						}
					}
				}
			}
		}
	}
}