找回密碼
 注冊帳號

掃一掃,訪問微社區

碧俐千仞 如果這篇文章說不清epoll的本質,那就過來掐死我吧! (2)

5
回復
1070
查看
打印 上一主題 下一主題
[ 復制鏈接 ]
排名
19948
昨日變化

41

主題

222

帖子

877

積分

Rank: 9Rank: 9Rank: 9

UID
53741
好友
39
蠻牛幣
2648
威望
0
注冊時間
2014-11-6
在線時間
208 小時
最后登錄
2019-8-9

專欄作家

馬上注冊,結交更多好友,享用更多功能,讓你輕松玩轉社區。

您需要 登錄 才可以下載或查看,沒有帳號?注冊帳號

x
本帖最后由 tyxxxx 于 2019-5-25 16:19 編輯

從事服務端開發,少不了要接觸網絡編程。epoll作為linux下高性能網絡服務器的必備技術至關重要,大部分游戲服務器都使用到這一多路復用技術。文章核心思想是:要讓讀者清晰明白EPOLL為什么性能好。

文/羅培羽


上篇回顧

一、從網卡接收數據說起

二、如何知道接收了數據?

三、進程阻塞為什么不占用cpu資源?


四、內核接收網絡數據全過程

這一步,貫穿網卡、中斷、進程調度的知識,敘述阻塞recv下,內核接收數據全過程。

如下圖所示,進程在recv阻塞期間,計算機收到了對端傳送的數據(步驟①)。數據經由網卡傳送到內存(步驟②),然后網卡通過中斷信號通知cpu有數據到達,cpu執行中斷程序(步驟③)。此處的中斷程序主要有兩項功能,先將網絡數據寫入到對應socket的接收緩沖區里面(步驟④),再喚醒進程A(步驟⑤),重新將進程A放入工作隊列中。


內核接收數據全過程

喚醒進程的過程如下圖所示。


喚醒進程

以上是內核接收數據全過程

這里留有兩個思考題,大家先想一想。

其一,操作系統如何知道網絡數據對應于哪個socket?

其二,如何同時監視多個socket的數據?




(——我是分割線,想好了才能往下看哦~)



公布答案的時刻到了。

第一個問題:因為一個socket對應著一個端口號,而網絡數據包中包含了ip和端口的信息,內核可以通過端口號找到對應的socket。當然,為了提高處理速度,操作系統會維護端口號到socket的索引結構,以快速讀取。

第二個問題是多路復用的重中之重,是本文后半部分的重點!


五、同時監視多個socket的簡單方法

服務端需要管理多個客戶端連接,而recv只能監視單個socket,這種矛盾下,人們開始尋找監視多個socket的方法。epoll的要義是高效的監視多個socket。從歷史發展角度看,必然先出現一種不太高效的方法,人們再加以改進。只有先理解了不太高效的方法,才能夠理解epoll的本質。

假如能夠預先傳入一個socket列表,如果列表中的socket都沒有數據,掛起進程,直到有一個socket收到數據,喚醒進程。這種方法很直接,也是select的設計思想。

為方便理解,我們先復習select的用法。在如下的代碼中,先準備一個數組(下面代碼中的fds),讓fds存放著所有需要監視的socket。然后調用select,如果fds中的所有socket都沒有數據,select會阻塞,直到有一個socket接收到數據,select返回,喚醒進程。用戶可以遍歷fds,通過FD_ISSET判斷具體哪個socket收到數據,然后做出處理。



  • [AppleScript] 純文本查看 復制代碼
    int s = socket(AF_INET, SOCK_STREAM, 0);  
    bind(s, ...)
    listen(s, ...)
    int fds[] =  存放需要監聽的socket
    while(1){
        int n = select(..., fds, ...)
        for(int i=0; i < fds.count; i++){
            if(FD_ISSET(fds, ...)){
    
                //fds的數據處理
    
            }
    
        }
    
    }
    


select的流程

select的實現思路很直接。假如程序同時監視如下圖的sock1、sock2和sock3三個socket,那么在調用select之后,操作系統把進程A分別加入這三個socket的等待隊列中。


操作系統把進程A分別加入這三個socket的等待隊列中

當任何一個socket收到數據后,中斷程序將喚起進程。下圖展示了sock2接收到了數據的處理流程。

ps:recv和select的中斷回調可以設置成不同的內容。


sock2接收到了數據,中斷程序喚起進程A

所謂喚起進程,就是將進程從所有的等待隊列中移除,加入到工作隊列里面。如下圖所示。


將進程A從所有等待隊列中移除,再加入到工作隊列里面

經由這些步驟,當進程A被喚醒后,它知道至少有一個socket接收了數據。程序只需遍歷一遍socket列表,就可以得到就緒的socket。

這種簡單方式行之有效,在幾乎所有操作系統都有對應的實現。


但是簡單的方法往往有缺點,主要是:

其一,每次調用select都需要將進程加入到所有監視socket的等待隊列,每次喚醒都需要從每個隊列中移除。這里涉及了兩次遍歷,而且每次都要將整個fds列表傳遞給內核,有一定的開銷。正是因為遍歷操作開銷大,出于效率的考量,才會規定select的最大監視數量,默認只能監視1024個socket。

其二,進程被喚醒后,程序并不知道哪些socket收到數據,還需要遍歷一次。

那么,有沒有減少遍歷的方法?有沒有保存就緒socket的方法?這兩個問題便是epoll技術要解決的。


補充說明: 本節只解釋了select的一種情形。當程序調用select時,內核會先遍歷一遍socket,如果有一個以上的socket接收緩沖區有數據,那么select直接返回,不會阻塞。這也是為什么select的返回值有可能大于1的原因之一。如果沒有socket有數據,進程才會阻塞。
六、epoll的設計思路

epoll是在select出現N多年后才被發明的,是select和poll的增強版本。epoll通過以下一些措施來改進效率。

措施一:功能分離

select低效的原因之一是將“維護等待隊列”和“阻塞進程”兩個步驟合二為一。如下圖所示,每次調用select都需要這兩步操作,然而大多數應用場景中,需要監視的socket相對固定,并不需要每次都修改。epoll將這兩個操作分開,先用epoll_ctl維護等待隊列,再調用epoll_wait阻塞進程。顯而易見的,效率就能得到提升。


相比select,epoll拆分了功能

為方便理解后續的內容,我們先復習下epoll的用法。如下的代碼中,先用epoll_create創建一個epoll對象epfd,再通過epoll_ctl將需要監視的socket添加到epfd中,最后調用epoll_wait等待數據。

[AppleScript] 純文本查看 復制代碼
int s = socket(AF_INET, SOCK_STREAM, 0);   
bind(s, ...)
listen(s, ...)
int epfd = epoll_create(...);
epoll_ctl(epfd, ...); //將所有需要監聽的socket添加到epfd中
while(1){
    int n = epoll_wait(...)

   for(接收到數據的socket){
        //處理
    }
}

功能分離,使得epoll有了優化的可能。


措施二:就緒列表

select低效的另一個原因在于程序不知道哪些socket收到數據,只能一個個遍歷。如果內核維護一個“就緒列表”,引用收到數據的socket,就能避免遍歷。如下圖所示,計算機共有三個socket,收到數據的sock2和sock3被rdlist(就緒列表)所引用。當進程被喚醒后,只要獲取rdlist的內容,就能夠知道哪些socket收到數據。


就緒列表示意圖

以下內容待續

七、epoll的原理和流程

八、epoll的實現細節

九、結論


《網絡游戲實戰(第2版)》是一本專門介紹如何開發多人網絡游戲的書籍,用實例介紹開發游戲的全過程,非常實用。書中對網絡編程有詳細的講解,全書用一個大例子貫穿,真正的“實戰”教程。


回復

使用道具 舉報

7日久生情
2062/5000
排名
4092
昨日變化

0

主題

1345

帖子

2062

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
254705
好友
1
蠻牛幣
1877
威望
0
注冊時間
2017-11-16
在線時間
355 小時
最后登錄
2019-8-9
沙發
2019-5-25 22:59:02 只看該作者
66666666666666666666666666666666666
回復 支持 反對

使用道具 舉報

7日久生情
3175/5000
排名
295
昨日變化

0

主題

600

帖子

3175

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
88896
好友
0
蠻牛幣
12821
威望
0
注冊時間
2015-4-2
在線時間
677 小時
最后登錄
2019-8-3
板凳
2019-5-26 10:25:51 只看該作者
好聞,又復習了一下網絡編程
回復 支持 反對

使用道具 舉報

7日久生情
2915/5000
排名
2230
昨日變化

1

主題

1891

帖子

2915

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
119154
好友
0
蠻牛幣
3240
威望
0
注冊時間
2015-8-21
在線時間
393 小時
最后登錄
2019-8-9
地板
2019-5-26 23:40:11 只看該作者
謝謝樓主大大。
回復

使用道具 舉報

6蠻牛粉絲
1145/1500
排名
2066
昨日變化

0

主題

252

帖子

1145

積分

Rank: 6Rank: 6Rank: 6

UID
26073
好友
1
蠻牛幣
4354
威望
0
注冊時間
2014-5-21
在線時間
225 小時
最后登錄
2019-8-9
5#
2019-5-27 08:40:50 只看該作者
66666666,多謝知識傳播者!
回復 支持 反對

使用道具 舉報

5熟悉之中
707/1000
排名
10706
昨日變化

0

主題

458

帖子

707

積分

Rank: 5Rank: 5

UID
301976
好友
1
蠻牛幣
1059
威望
0
注冊時間
2018-10-31
在線時間
151 小時
最后登錄
2019-8-9
6#
2019-6-13 13:53:17 只看該作者
服務器大佬,感謝只是分享...
回復 支持 反對

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 注冊帳號

本版積分規則

女校游泳队彩金