16.IO複用之epoll函數


前面介紹了select函數和poll函數, select我們知道能夠支持的套接字個數太少了, 但是poll函數已經很好了也沒有什麼缺點啊爲啥還要介紹epoll呢? 接下來我們就來談談pollselect函數的其他問題.


poll和select的問題

  1. poll函數一般使用都是將它設置爲輪詢的方式, 這樣是很佔用CPU的; 當然可以將其設置爲阻塞也就能避免這個問題.

  2. select能夠監聽的描述符太少.

  3. pollselect的底層實現. poll函數其實和select底層實現類似.

    當有監聽的事件到來時, select會將該事件的描述符和事件的狀態從內核複製到用戶空間中. poll會將到來事件的描述符狀態複製到用戶空間.

    最後用戶都要重新遍歷所有的描述符判斷是哪一個描述符狀態改變.


epoll的優點

poll和select最大的問題都是實現上需要佔用CPU大量的時間, 那麼epoll又是怎樣避免這樣的問題呢.

  1. epoll函數也要設置監聽描述符並將其複製到內核中爲每一個監聽描述符註冊一個回調函數.
  2. 當監聽描述符的時候到來時直接返回該描述符的回調函數不需要在進行復制.

可以看出來epoll只有在設置監聽時才複製一次描述符, 事件到來時不需要複製並且也不需要遍歷判斷. 大大的減少了佔用CPU的時間.

如果對於底層實現感興趣可以看一下以前總結的epoll源碼分析select源碼分析.


函數原型

epoll可是有三個函數哦. 我們一個個來介紹並探討他們的底層實現的功能.

1. epoll_create

#include <sys/epoll.h>

int epoll_create(int size);

成功 : 返回一個文件描述符.

失敗 : 返回-1.

功能 : 在cache中申請創建的紅黑樹大小.

參數

  • size : 該參數 原本是指定在cache中分配的內存大小, 在linux2.6以上已經沒有用了.

2. epoll_ctl

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

成功 : 返回0

失敗 : 返回-1

功能 : 註冊要監聽的事件類型.

參數 :

  • epfd : epoll_create函數返回的文件描述符.

  • opt : 功能選項, 用三個宏定義表示不同的功能

    opt值 描述
    EPOLL_CTL_ADD 註冊fd的回調函數到epfd中
    EPOLL_CTL_DEL 刪除fd註冊的回調函數
    EPOLL_CTL_MOD 修改已註冊的fd的監聽事件
  • fd : 需要監聽的文件描述符

  • event : 內核需要監聽的事件, 與poll的事件類似.

    event值 描述
    EPOLLIN 監聽是描述符是否可讀
    EPOLLOUT 監聽是描述符是否可寫
    EPOLLERR 發生錯誤, 對端異常斷開
    EPOLLRDHUP 對端掛斷, 或其中一端關閉了
    EPOLLET [1] 設置爲邊沿觸發模式
    EPOLLONESHOT [2] 設置關聯文件描述符的一次性行爲

3. epoll_wait

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

成功 : 返回0

失敗 : 返回-1

功能 : 從就緒事件的鏈表中返回監聽的回調函數.

參數

  • epfd : epoll_create函數返回的文件描述符.
  • events : 內核返回的監聽事件
  • maxevents : events數組的大小
  • timeout : 超時時間
    1. timeout == INFTIM(負數) : select函數永遠阻塞等待監視文件描述符集合中某個文件描述符發生變化爲止.
    2. timeout == 0 : 函數爲非阻塞函數, 不管有無等待的文件描述符發生變化都會返回
    3. timeout > 0 : 等待的超時時間, 即函數在timeout時間內阻塞, 超時時間之內有事件到來就返回了, 否則在超時後不管怎樣一定返回.

函數調用

看了前面的三個函數估計第一次接觸可能會產生牴觸, 太麻煩了. 但是麻煩歸麻煩, 好用高效率纔是王道. 比如在百萬連接時, epoll_wait都可以只需要常量時間就能精確返回觸發事件的套接字.

說了那麼多, 還是練習能最快掌握, 完整代碼 epoll_service.c

void doService(int servicefd)
{
    int clientfd, epfd;
    char buf[1024];

  	// 設置監聽套接字
    struct epoll_event event, evts[EPOLL_MAX];
    event.events = EPOLLIN;
    event.data.fd = servicefd;	// 這裏需要設置監聽的套接字
    epfd = epoll_create(1);
    epoll_ctl(epfd, EPOLL_CTL_ADD, servicefd, &event);	// 這裏也需要設置監聽的套接字

    int n, eventNum;
    while(1)
    {
        // 設置爲永久阻塞
		eventNum = epoll_wait(epfd, evts, EPOLL_MAX, -1);
		if(eventNum == 0)
		    continue;
		else if(eventNum < 0)
		    EXIT("epoll_wait");
	        // 遍歷狀態改變的套接字
		for(int i = 0; i < eventNum; i++)
		{
		    if(evts[i].data.fd == servicefd && (evts[i].events &EPOLLIN))
		    {
				clientfd = Accept(servicefd, NULL, NULL);
				event.events = EPOLLIN;
				event.data.fd = clientfd;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &event);
		    }
		    else if(evts[i].events & EPOLLIN)
		    {
				n = recv(evts[i].data.fd, buf, sizeof(buf), 0);
				if(n <= 0)
				{
		            // 刪除關閉的套接字
					    epoll_ctl(epfd, EPOLL_CTL_DEL, evts[i].data.fd, NULL);
				    close(evts[i].data.fd);
				    fprintf(stderr, "peer close\n");
				}
				send(evts[i].data.fd, buf, n, 0);
		    }
		}
    }
}

具體現象與select 是一樣, 這裏就不再驗證了.

總結

本節介紹了epoll的三個函數, 希望能夠掌握並且自己修改服務端的函數. 接下來我們還要繼續介紹epoll的功能.

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