Python高級網絡編程系列之第三篇

  在高級篇二中,咱們講解了5中經常使用的IO模型,理解這些經常使用的IO模型,對於編寫服務器程序有很大的幫助,能夠提升咱們的併發速度!由於在網絡中通訊主要的部分就是IO操做。在這一篇當中咱們會重點講解在第二篇當中提到的IO複用模型,即select機制。其實select機制有一些缺陷,後來產生了一種更加高效的機制epoll,稍後會講解!html

 

1、select機制web

  1. 原理:select能夠理解成一個監聽器,能夠監聽多個文件描述符。當某個文件描述符的狀態發生改變了(可讀/可寫),操做系統就會發送消息給應用程序,去處理數據。編程

  2. 優勢:幾乎全部平臺都支持,跨平臺支持性較好。數組

  3. 缺點:服務器

    (1). 當個進程/線程可監視的文件描述符數量有限制。網絡

    (2). 對文件描述符的掃描是線性的,採用輪詢的方式,每次都是從頭一直掃描到結尾,當文件描述符的列表變大時,會至關浪費時間和CPU併發

    (3). 把包含大量文件描述符的數組從內核空間拷貝到用戶空間,當數組小的時候可能還好,可是隨着數組的增大會變得很浪費資源。socket

  4. 水平觸發:tcp

    當select()把狀態發生變化的文件描述符報告給進程以後,若是進程沒有進行任何處理,那麼下次select()還會報告這些文件描述符。函數

 

2、epoll

  epoll能夠當作是select/poll(本質就是select)的增強版,打破了不少select的約束,以及添加了一些其餘的功能!

  1. 爲何epoll效率很高呢?

    epoll最大的特色是隻告訴服務器有哪些文件描述符(fd)發生了變化。若是服務器不去處理相應的fd,那麼操做系統就會把這個fd丟棄,再也不給服務器發送消息(邊緣觸發)!除此以外,epoll是採用事件監聽的方式通知,這也是epoll的魅力所在!

  2.原理:

    

    (1). 註冊在epoll中的文件描述符,操做系統的事件監聽會去監聽文件描述符集合(fd_set)

    (2). 若是有fd發生了變化,那麼事件監聽會向操做系統報告發生變化的fd

    (3). 操做系統會給服務器發送消息,通知它你關注的fd有變化,去處理吧

    (4). 此時服務器就去共享內存中讀取數據了!

  3. 優勢:

    (1). 沒有最大鏈接數的限制。

    (2). 不採用輪詢的方式去處理fd,而是採用事件監聽的方式,即哪一個fd有事件發生,OS通知服務器使用相應的回調函數來處理fd

    (3). 內存拷貝:當有數據到來時,操做系統會給服務器發送通知去處理數據。經過採用共享內存的方式加快用戶空間與內核空間消息的傳遞速度。

  4. 誤區:

    並非在任何狀況下,epoll都要比select/poll高效,只有當不少鏈接請求到來時纔會很高效!

 

3、epoll編程模型

  (1). 建立1個epoll對象

  (2). 告訴epoll對象,在指定的fd上監聽指定的事件

  (3). 詢問epoll對象,自從上次查詢後,哪些fd上發生了哪些事件  

  (4). 在這些fd上執行一些操做

  (5). 告訴epoll對象,修改fd列表或註冊事件,並監控

  (6). 重複步驟3-5,直到完成

  (7). 銷燬epoll對象

  

 

4、代碼實現

 1 """
 2 利用非阻塞和epoll來實現一個服務器  3 """
 4 import socket  5 
 6 import select  7 
 8 
 9 class WebServer: 10     """定義一個web服務器"""
11 
12     def __init__(self): 13         # 1.建立TCP 服務器
14         self.tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 15         # 複用端口
16         self.tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 17         # 2.綁定端口
18         self.tcp_server.bind(('', 6767)) 19         # 3.設爲被動套接字
20         self.tcp_server.listen(128) 21 
22     def run(self): 23         """運行一個服務器"""
24         #1.把服務器設置爲非阻塞模式
25  self.tcp_server.setblocking(False) 26         #2.建立一個epoll對象併爲服務器註冊一個可接受鏈接的事件
27         epoll = select.epoll() 28  epoll.register(self.tcp_server.fileno(), select.EPOLLIN) 29         client_dict = dict() # 讓fd與client創建關聯
30         #3.服務器接受客戶端的請求
31         while True: 32             # 4.監聽epoll中哪一個fd發生了什麼事件
33             epoll_list = epoll.poll() 34             for fd, event in epoll_list: 35                 if fd == self.tcp_server.fileno(): 36                     # 有客戶端來鏈接被動套接字服務器
37                     client, addr = self.tcp_server.accept() 38                     # print(addr)
39                     # 把客戶端註冊到epoll中
40  epoll.register(client.fileno(), select.EPOLLIN) 41                     # 把客戶端和客戶端對應的fd添加到client字典中去
42                     client_dict[client.fileno()] = client 43                 else: 44                     # 有客戶端發送數據過來,可是該如何去得到這個客戶端呢?
45                     data = client_dict[fd].recv(1024).decode('utf-8') 46                     if data: 47                         # 說明客戶端發送數據過來了
48                         print(data) 49                         client_dict[fd].send('我已經收到你的數據了!\n'.encode('utf-8')) 50                     else: 51                         # 說明客戶端已經關閉了
52  client_dict[fd].close() 53                         
54  client_dict.popitem() 55                         # 須要把該客戶端註冊的事件取消掉
56  epoll.unregister(fd) 57 
58             # 遍歷client字典中每一個客戶端對應的fd
59             for item in client_dict.items(): 60                
61                 print('fd:{}--->addr:{}'.format(item[0], item[1])) 62                 print('-'*50) 63         # 關閉服務器
64  self.tcp_server.close() 65 
66 
67 
68 def main(): 69     #1.初始化一個TCP服務器
70     server = WebServer() 71     #2.運行一個服務器
72  server.run() 73 
74 
75 if __name__ == '__main__': 76     main()
相關文章
相關標籤/搜索