Python網絡編程(4)——異步編程select & epoll

  在SocketServer模塊的學習中,咱們瞭解了多線程和多進程簡單Server的實現,使用多線程、多進程技術的服務端爲每個新的client鏈接建立一個新的進/線程,當client數量較多時,這種技術也將帶來巨大的開銷,服務器的內存畢竟是有限的,而客戶的量級可能很是龐大,所以爲每一個客戶端鏈接建立單獨的進/線程可能並不實際。python

  另外一種提高服務器性能的網絡編程模式是事件驅動的(異步)編程,這裏所說的「事件」一般是指:客戶端鏈接到來、套接字有可讀數據、套接字可寫等。服務器時刻處在一個時刻等待這些事件的循環中,一旦這些常見的某類事件發生,服務端分別進行必定的響應,而後繼續等待下一個事件的發生。編程

  Python 對事件驅動的網絡編程提供的支持包括:服務器

    select 模塊 —— 低層次 select, epoll 等異步編程機制;網絡

    asyncore, asynchat 模塊 —— 高層次的異步編程機制,在Python 3中合併爲 asyncio 模塊;多線程

    Twisted 框架 —— Twisted 是一個功能強大的Python事件驅動編程框架。app

  本文將會學習 select 模塊提供的 select、epoll 機制。框架

 

Select 編程異步

  select 的中文含義是」選擇「,select機制也如其名,監聽一些 server 關心的套接字、文件等對象,關注他們是否可讀、可寫、發生異常等事件。一旦出現某個 select 關注的事件,select 會對相應的套接字或文件進行特定的處理,這就是 select 機制最主要的功能。socket

  select 機制能夠只使用一個進程/線程來處理多個 socket 或其餘對象,所以又被稱爲I/O複用。async

  關於 select 機制的進程阻塞形式,與普通的套接字略有不一樣。socket 對象可能阻塞在 accept(), recvfrom()等方法上,以 recvfrom() 方法爲例,當執行到 socket.recvfrom() 這一句時,就會調用一個系統調用詢問內核:client / server 發來的數據包準備好了沒?此時從進程空間切換到內核地址空間,內核可能須要等數據包徹底到達,而後將數據複製到程序的地址空間後,recvfrom() 纔會返回,接下來進程繼續執行,對讀取到的數據進行必要的處理。

  而使用 select 函數編程時,一樣針對上面的 recvfrom() 方法,進程會阻塞在 select() 調用上,等待出現一個或多個套接字對象知足可讀事件,當內核將數據準備好後,select() 返回某個套接字對象可讀這一條件,隨後再調用 recvfrom() 將數據包從內核複製到進程地址空間。

  因此可見,若是僅僅從單個套接字的處理來看,select() 反倒性能更低,由於 select 機制使用兩個系統調用。但 select 機制的優點就在於它能夠同時等待多個 fd 就緒,而當某個 fd 發生知足咱們關心的事件時,就對它執行特定的操做。

  調用 select 模塊提供的 select 函數能夠實現 Select 編程:

select(inputs, outputs, excepts, timeout=None)

  參數說明:

  inputs, outputs, excepts 是分別由等待輸入事件、輸出事件和異常條件的 socket 對象組成的列表;

  timeout——數字或者None,上一篇文章中已經介紹過 Python套接字對象的超時行爲,這裏select與套接字的超時行爲相似:若是 timeout 是 None,則 select 在阻塞調用上會一直等待直到事件發生;若是 timeout 是一個非 0 數字,則 select 最多等待 timeout 秒;若是 timeout 是0,則 select 遇到阻塞的調用時會當即返回,一刻也不等待。

  返回值:

  select() 返回一個 (i, o, e) 形式的三元組,其中i, o, e 分別都是列表,列表 i 中的每一個元素都收到了 input 事件,列表 o 中的每一個元素都收到了 output 事件,列表 e 中的每一個元素則發生了異常。

  

  Select 編程的特色是 select() 參數中的 inputs, outputs, excepts 能夠不止 socket 對象,而只要是支持無參數調用 fileno() 方法,返回相應 fd 的對象都可,好比 SocketServer 模塊中提供的幾種 server 類型就支持這一方法,因此在 select() 參數的某個列表中,就能夠包含這些 Server 類型的實例。在 Unix 平臺上,select 機制也支持並不對應於 socket 對象的文件描述字。

例:

  使用 select 網絡編程模型實現一個簡單的回顯服務器

#!-*-encoding:utf-8-*-
import socket
import select

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 4424))
sock.listen(10)

ins = [sock, ]
ous = []
# socket -> data to send
data = {}
# socket -> client address, which is (host, port) tuple
adrs = {}

try:
    print("Select Server Start Working!")
    while True:
        i, o, e = select.select(ins, ous, [])
        for x in i: # 處理髮生 input 事件的套接字
            if x is sock:
                # 監聽套接字上發生input事件,說明有新的鏈接
                newSock, addr = sock.accept()
                print("Connected from: ", addr)
                ins.append(newSock)
                adrs[newSock] = addr
            else:
                # 鏈接套接字上發生 input 事件說明有數據可讀,或者是client端斷開鏈接
                newData  = x.recv(1024)
                if newData:
                    # 有新的數據到來,此時將發給該 client 的響應入隊
                    print("%d bytes from %s" %(len(newData), adrs[x]))
                    data[x] = data.get(x, "") + newData
                    if x not in ous:
                        ous.append(x)
                else:
                    # 鏈接套接字上發生 input 事件,若是不是有新數據可讀,說明是client斷開鏈接
                    print("Disconnected from: ", adrs[x])
                    del adrs[x]
                    try:
                        ous.remove(x)
                    except ValueError: pass
                    x.close()
                    ins.remove(x)
        for x in o: # 處理髮生 output 事件的套接字
            # 有 output 事件,說明此時可寫
            tosend = data.get(x)
            if tosend:
                nsent = x.send(tosend)
                print("%d bytes to %s" %(nsent, adrs[x]))
                tosend = tosend[nsent:]
            if tosend:
                print("%d bytes remain for %s" %(len(tosend), adrs[x]))
                data[x] = tosend
            else:
                try:
                    del data[x]
                except KeyError:
                    pass
                ous.remove(x)
                print("No data currently remain for:", adrs[x])
finally:
    sock.close()
相關文章
相關標籤/搜索