Linux網絡編程【六】:TCP協議高性能服務器(http)模型之I/O多路轉接epoll

什麼是epoll?

epoll是linux內核爲處理大批量文件描述符而作了改進的poll,是Linux下多路複用IO接口select/poll的增強版本,它能顯著提高程序在大

量併發連接中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要

遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。

注:epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空

間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。

注:epoll文件描述符用完後,直接用close關閉即可,非常方便。事實上,任何被偵聽的文件符只要其被關閉,那麼它也會自動從被偵

聽的文件描述符集合中刪除,很是智能。

epoll相關的系統調用有:epoll_create, epoll_ctl和epoll_wait。

epoll_create:


返回值:>0:非空文件描述符;
-1:函數調用失敗,同時會自動設置全局變量errno;
epoll_ctl:用來添加/修改/刪除需要偵聽的文件描述符及其事件.
epoll的事件註冊函數,它不同於select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。


op:表示動作,爲以下三個宏任意一個(根據需要):添加,修改,刪除



.epoll_wait:接收發生在被偵聽的描述符上的,用戶感興趣的IO事件。


收集在epoll監控的事件中已經就緒的事件。
events:是分配好的epoll_event結構體數組,epoll將會把就緒的事件賦值到events數組中(events不可以是空指針,內核只負責把數據複製到這個events數組中)。
maxevents:告之內核這個events有多大,這個 maxevents的值不能小於創建epoll_create()時的size
timeout:是超時時間(毫秒)
    1.0表示輪詢非阻塞,立即返回;
    2.-1將不確定,也有說法說是永久阻塞。
    3.大於0,以timeput事件輪詢返回
返回值:
    1.成功,返回對應I/O上已準備好的文件描述符數
    2.0表示已超時。
    3.-1,發生錯誤。

epoll的優點:
本身沒有最大併發連接的限制,僅受系統中進程能打開的最大文件數目限制;
效率提升:只有活躍的socket纔會主動的去調用callback函數;
省去不必要的內存拷貝:epoll通過內核與用戶空間mmap同一塊內存實現。
當然,以上的優缺點僅僅是特定場景下的情況:高併發,且任一時間只有少數socket是活躍的。





服務器具體實現代碼:

(客戶端代碼在前幾篇博客寫過幾個版本,在此不再贅述)

/*************************************************************************
	> File Name: epoll.c
	> Author: liumin
	> Mail: [email protected] 
	> Created Time: Sun 11 Jun 2017 02:42:50 PM CST
 ************************************************************************/

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

static void Usage(char* proc)
{
	printf("Usage: %s [local_ip] [local_port]\n",proc);
}

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

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

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

	if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
	{
		perror("bind");
		return 3;
	}

	if(listen(sock, 10) < 0)
	{
		perror("listen");
		return 4;
	}
	return sock;
}

typedef struct fd_buf
{
	int fd;
	char buf[10240];
}fd_buf_t,*fd_buf_p;

static void* alloc_fd_buf(int fd)
{
	fd_buf_p tmp = (fd_buf_p)malloc(sizeof(fd_buf_t));
	if(!tmp)
	{
		perror("malloc");
		return NULL;
	}
	tmp->fd = fd;
	return tmp;
}

int main(int argc, char* argv[])
{
	if(argc != 3)
	{
		Usage(argv[0]);
		return 1;
	}

	int listen_sock = startup(argv[1], atoi(argv[2]));

	int epollfd = epoll_create(256);
	if(epollfd < 0)
	{
		perror("epoll_create");
		close(listen_sock);
		return 5;
	}

	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.ptr = alloc_fd_buf(listen_sock);
    
	epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev);

	int nums = 0;
	struct epoll_event evs[256];
	int timeout = -1;

	while(1)
	{
		switch(nums = epoll_wait(epollfd, evs, 256, timeout))
		{
			case -1:
				perror("epoll_wait");
				break;
			case 0:
				printf("timeout...\n");
				break;
			default:
				{
					printf("nums : %d \n",nums);
					int i = 0;
					for(; i < nums; i++)
					{
						fd_buf_p fp = (fd_buf_p)evs[i].data.ptr;
						if(fp->fd == listen_sock && (evs[i].events & EPOLLIN))
						{
							struct sockaddr_in client;
							socklen_t len = sizeof(client);
							int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
							if(new_sock < 0)
							{
								perror("accept");
								continue;
							}
							printf("get a new client: ip:%s ,port:%d \n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));

							ev.events = EPOLLIN;
							ev.data.ptr = alloc_fd_buf(new_sock);
							epoll_ctl(epollfd, EPOLL_CTL_ADD, new_sock, &ev);
						}//if
						else if(fp->fd != listen_sock)
						{
			 				if(evs[i].events & EPOLLIN)
							{
		 						ssize_t s = read(fp->fd, fp->buf, sizeof(fp->buf));
								if(s > 0)
								{
	 								fp->buf[s] = 0;
									printf("client say# %s\n", fp->buf);
									ev.events = EPOLLOUT;
									ev.data.ptr = fp;
									epoll_ctl(epollfd, EPOLL_CTL_MOD, fp->fd, &ev);
								}
								else if(s <= 0)
								{
	 								printf("client quit!\n");
									close(fp->fd);
									epoll_ctl(epollfd, EPOLL_CTL_DEL, fp->fd, NULL);
									free(fp);
								}
								else
								{}
							}
							else if(evs[i].events & EPOLLOUT)
							{
					 			const char* msg = "HTTP/1.0 200 OK\r\n\r\n<html><h1>hello epoll!</h1><html>";
								write(fp->fd, msg, strlen(msg));
								close(fp->fd);
								//這裏主要用於瀏覽器測試,所以在輸出後關閉文件描述符
								//若使用客戶端、Telnet遠程登錄測試 可以去掉 實現連續發送信息  
								epoll_ctl(epollfd, EPOLL_CTL_DEL, fp->fd, NULL);
								free(fp);
							}
							else
							{}
						}
						else
						{}
					}//for
				}
				break;
		}
	}

	return 0;
}


發佈了63 篇原創文章 · 獲贊 24 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章