Select 模型簡介

http://python.jobbole.com/84058/

多路複用I/O

簡明網絡I/O模型文章能夠知道經常使用的IO模型。其中同步模型中,使用多路複用I/O能夠提升服務器的性能。node

在多路複用的模型中,比較經常使用的有select模型和poll模型。這兩個都是系統接口,由操做系統提供。固然,Pythonselect模塊進行了更高級的封裝。selectpoll的底層原理都差很少。下面就介紹selectpython

select 原理

網絡通訊被Unix系統抽象爲文件的讀寫,一般是一個設備,由設備驅動程序提供,驅動能夠知道自身的數據是否可用。支持阻塞操做的設備驅動一般會實現一組自身的等待隊列,如讀/寫等待隊列用於支持上層(用戶層)所需的blocknon-block操做。設備的文件的資源若是可用(可讀或者可寫)則會通知進程,反之則會讓進程睡眠,等到數據到來可用的時候,再喚醒進程。nginx

這些設備的文件描述符被放在一個數組中,而後select調用的時候遍歷這個數組,若是對於的文件描述符可讀則會返回改文件描述符。當遍歷結束以後,若是仍然沒有一個可用設備文件描述符,select讓用戶進程則會睡眠,直到等待資源可用的時候在喚醒,遍歷以前那個監視的數組。每次遍歷都是線性的。數組

select 回顯服務器

select涉及系統調用和操做系統相關的知識,所以單從字面上理解其原理仍是比較乏味。用代碼來演示最好不過了。使用pythonselect模塊很容易寫出下面一個回顯服務器:服務器

1網絡

2app

3curl

4異步

5socket

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

import select

import socket

import sys

 

HOST = 'localhost'

PORT = 5000

BUFFER_SIZE = 1024

 

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((HOST, PORT))

server.listen(5)

 

inputs = [server, sys.stdin]

running = True

 

while True:

    try:

        # 調用 select 函數,阻塞等待

        readable, writeable, exceptional = select.select(inputs, [], [])

    except select.error, e:

        break

 

    # 數據抵達,循環

    for sock in readable:

        # 創建鏈接

        if sock == server:

            conn, addr = server.accept()

            # select 監聽的socket

            inputs.append(conn)

        elif sock == sys.stdin:

            junk = sys.stdin.readlines()

            running = False

        else:

            try:

                # 讀取客戶端鏈接發送的數據

                data = sock.recv(BUFFER_SIZE)

                if data:

                    sock.send(data)

                    if data.endswith('\r\n\r\n'):

                        # 移除select監聽的socket

                        inputs.remove(sock)

                        sock.close()

                else:

                    # 移除select監聽的socket

                    inputs.remove(sock)

                    sock.close()

            except socket.error, e:

                inputs.remove(sock)

 

server.close()

運行上述代碼,使用curl訪問http://localhost:5000,便可看命令行返回請求的HTTP request信息。

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

1

2

3

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((HOST, PORT))

server.listen(5)

上述代碼使用socket初始化一個TCP套接字,並綁定主機地址和端口,而後設置服務器監聽。

1

inputs = [server, sys.stdin]

這裏定義了一個須要select監聽的列表,列表裏面是須要監聽的對象(等於系統監聽的文件描述符)。這裏監聽socket套接字和用戶的輸入。

而後代碼進行一個服務器無線循環。

1

2

3

4

5

try:

    # 調用 select 函數,阻塞等待

    readable, writeable, exceptional = select.select(inputs, [], [])

except select.error, e:

    break

調用了select函數,開始循環遍歷監聽傳入的列表inputs。若是沒有curl服務器,此時沒有創建tcp客戶端鏈接,所以改列表內的對象都是數據資源不可用。所以select阻塞不返回。

客戶端輸入curl http://localhost:5000以後,一個套接字通訊開始,此時input中的第一個對象server由不可用變成可用。所以select函數調用返回,此時的readable有一個套接字對象(文件描述符可讀)。

1

2

3

4

5

6

for sock in readable:

    # 創建鏈接

    if sock == server:

        conn, addr = server.accept()

        # select 監聽的socket

        inputs.append(conn)

select返回以後,接下來遍歷可讀的文件對象,此時的可讀中只有一個套接字鏈接,調用套接字的accept()方法創建TCP三次握手的鏈接,而後把該鏈接對象追加到inputs監視列表中,表示咱們要監視該鏈接是否有數據IO操做。

因爲此時readable只有一個可用的對象,所以遍歷結束。再回到主循環,再次調用select,此時調用的時候,不只會遍歷監視是否有新的鏈接須要創建,仍是監視剛纔追加的鏈接。若是curl的數據到了,select再返回到readable,此時在進行for循環。若是沒有新的套接字,將會執行下面的代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

try:

    # 讀取客戶端鏈接發送的數據

    data = sock.recv(BUFFER_SIZE)

    if data:

        sock.send(data)

        if data.endswith('rnrn'):

            # 移除select監聽的socket

            inputs.remove(sock)

            sock.close()

    else:

        # 移除select監聽的socket

        inputs.remove(sock)

        sock.close()

except socket.error, e:

    inputs.remove(sock)

經過套接字鏈接調用recv函數,獲取客戶端發送的數據,當數據傳輸完畢,再把監視的inputs列表中除去該鏈接。而後關閉鏈接。

整個網絡交互過程就是如此,固然這裏若是用戶在命令行中輸入中斷,inputs列表中監視的sys.stdin也會讓select返回,最後也會執行下面的代碼:

1

2

3

elif sock == sys.stdin:

    junk = sys.stdin.readlines()

    running = False

有人可能有疑問,在程序處理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模型。

許多高性能的軟件如nginxnodejs都是基於epoll進行的異步。

相關文章
相關標籤/搜索