Select 模型簡介

發表於2016-01-09

多路複用I/O

簡明網路I/O模型文章可以知道常用的IO模型。其中同步模型中,使用多路複用I/O可以提高伺服器的效能。

在多路複用的模型中,比較常用的有select模型和poll模型。這兩個都是系統介面,由作業系統提供。當然,Pythonselect模組進行了更高階的封裝。selectpoll的底層原理都差不多。下面就介紹select

select 原理

網路通訊被Unix系統抽象為檔案的讀寫,通常是一個裝置,由裝置驅動程式提供,驅動可以知道自身的資料是否可用。支援阻塞操作的裝置驅動通常會實現一組自身的等待佇列,如讀/寫等待佇列用於支援上層(使用者層)所需的blocknon-block操作。裝置的檔案的資源如果可用(可讀或者可寫)則會通知程式,反之則會讓程式睡眠,等到資料到來可用的時候,再喚醒程式。

這些裝置的檔案描述符被放在一個陣列中,然後select呼叫的時候遍歷這個陣列,如果對於的檔案描述符可讀則會返回改檔案描述符。當遍歷結束之後,如果仍然沒有一個可用裝置檔案描述符,select讓使用者程式則會睡眠,直到等待資源可用的時候在喚醒,遍歷之前那個監視的陣列。每次遍歷都是線性的。

select 回顯伺服器

select涉及系統呼叫和作業系統相關的知識,因此單從字面上理解其原理還是比較乏味。用程式碼來演示最好不過了。使用pythonselect模組很容易寫出下面一個回顯伺服器:

執行上述程式碼,使用curl訪問http://localhost:5000,即可看命令列返回請求的HTTP request資訊。

下面詳細解析上述程式碼的原理。

上述程式碼使用socket初始化一個TCP套接字,並繫結主機地址和埠,然後設定伺服器監聽。

這裡定義了一個需要select監聽的列表,列表裡面是需要監聽的物件(等於系統監聽的檔案描述符)。這裡監聽socket套接字和使用者的輸入。

然後程式碼進行一個伺服器無線迴圈。

呼叫了select函式,開始迴圈遍歷監聽傳入的列表inputs。如果沒有curl伺服器,此時沒有建立tcp客戶端連線,因此改列表內的物件都是資料資源不可用。因此select阻塞不返回。

客戶端輸入curl http://localhost:5000之後,一個套接字通訊開始,此時input中的第一個物件server由不可用變成可用。因此select函式呼叫返回,此時的readable有一個套接字物件(檔案描述符可讀)。

select返回之後,接下來遍歷可讀的檔案物件,此時的可讀中只有一個套接字連線,呼叫套接字的accept()方法建立TCP三次握手的連線,然後把該連線物件追加到inputs監視列表中,表示我們要監視該連線是否有資料IO操作。

由於此時readable只有一個可用的物件,因此遍歷結束。再回到主迴圈,再次呼叫select,此時呼叫的時候,不僅會遍歷監視是否有新的連線需要建立,還是監視剛才追加的連線。如果curl的資料到了,select再返回到readable,此時在進行for迴圈。如果沒有新的套接字,將會執行下面的程式碼:

通過套接字連線呼叫recv函式,獲取客戶端傳送的資料,當資料傳輸完畢,再把監視的inputs列表中除去該連線。然後關閉連線。

整個網路互動過程就是如此,當然這裡如果使用者在命令列中輸入中斷,inputs列表中監視的sys.stdin也會讓select返回,最後也會執行下面的程式碼:

有人可能有疑問,在程式處理sock連線的是時候,假設又輸入了curl對伺服器請求,將會怎麼辦?此時毫無疑問,inputs裡面的server套接字會變成可用。等現在的for迴圈處理完畢,此時select呼叫就會返回server。如果inputs裡面還有上一個過程的conn連線,那麼也會迴圈遍歷inputs的時候,再一次針對新的套接字acceptinputs列表進行監視,然後繼續迴圈處理之前的conn連線。如此有條不紊的進行,直到for迴圈結束,進入主迴圈呼叫select

任何時候,inputs監聽的物件有資料,下一次呼叫select的時候,就會繁返回readable,只要返回,就會對readable進行for迴圈,直到for迴圈結束在進行下一次select

主要注意,套接字建立連線是一次IO,連線的資料抵達也是一次IO

select的不足

儘管select用起來挺爽,跨平臺的特性。但是select還是存在一些問題。
select需要遍歷監視的檔案描述符,並且這個描述符的陣列還有最大的限制。隨著檔案描述符數量的增長,使用者態和核心的地址空間的複製所引發的開銷也會線性增長。即使監視的檔案描述符長時間不活躍了,select還是會線性掃描。

為了解決這些問題,作業系統又提供了poll方案,但是poll的模型和select大致相當,只是改變了一些限制。目前Linux最先進的方式是epoll模型。

許多高效能的軟體如nginx, nodejs都是基於epoll進行的非同步。

相關文章