上一篇文章中,談了一些網絡編程的基本概念。在現實使用中,用的最多的就是I/O復用了,無非就是select,poll,epoll
很多人提到網絡就說epoll,認為epoll效率是最高的。單純的這么認為,其實有失偏頗。epoll固然高效,可是它是怎么做到高效的,它到底比select或poll優異在哪兒?
我們通過調用流程來簡單分析下。
首先以select為例(poll類似),看下其調用過程
1.選擇想要處理的套接字,通過接口FD_SET(fd, &set)加入到set中;
2.調用select(max+1, &set,,..)
3.對set中所有套接字調用FD_ISSET(fd,&set),查看fd上是否有事件發生
select存在的問題
- 單個進程能夠監視的文件描述符的數量存在最大限制,通常是1024,當然可以更改數量,但由于select采用輪詢的方式掃描文件描述符,文件描述符數量越多,性能越差;(在linux內核頭文件中,有這樣的定義:#define __FD_SETSIZE 1024)
- 內核 / 用戶空間內存拷貝問題,select需要復制大量的句柄數據結構,產生巨大的開銷;
- select返回的是含有整個句柄的數組,應用程序需要遍歷整個數組才能發現哪些句柄發生了事件;
- select的觸發方式是水平觸發,應用程序如果沒有完成對一個已經就緒的文件描述符進行IO操作,那么之后每次select調用還是會將這些文件描述符通知進程。
epoll調用過程
1 .epoll_create 創建一個epoll對象,一般epollfd = epoll_create()
2 .epoll_ctl (epoll_add/epoll_del的合體),往epoll對象中增加/刪除某一個流的某一個事件
比如epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注冊緩沖區非空事件,即有數據流入
epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注冊緩沖區非滿事件,即流可以被寫入
添加事件的時候,其實是向內核注冊了一個回調函數。回調函數作用是,在相應的套接字上發生事件時,將其加入到epoll對象的時間就緒鏈表中,而這是在內核完成的。
3 epoll_wait(epollfd,...),獲取就緒事件。即從就緒事件鏈表中取出所有的事件。
可以看到epoll比select高效的地方在于,其返回的就是所有已經發生事件的套接字,而不需要像select那樣需要在用戶態去判斷每個套接字上是否有事件發生。
另外,在調用select時,內核需要去一一檢測傳入的套接字集合是否有事件,而調用epoll_wait時,只是將內核中的就緒數據取出而已
如果有n個連接,并且這n個連接都有事件發生,那么使用select與epoll其實并沒有多少區別。對于select來說,用戶態對每一個套接字的事件監測都是有效的。
但是select有一個問題是,每次去調用select之前,都要重置套接字set。如果連接數很大,每次FD_SET(fd, &set)調用接口,也會對性能造成不小的影響。而epoll中,只需調用一次epoll_ctl即可。
所以,在連接數很大,且活躍連接不多的情況下,使用epoll有明顯的優勢;而如果連接數較少,且連接基本都是活躍的,其實select的效果反而會更好。
![]() |
不含病毒。www.avast.com |