Part9 - 異步IO

本節內容

  1. 事件驅動
  2. IO多路複用
    1. 用戶空間和內存空間
    2. 進程切換
    3. 進程的阻塞
    4. 文件描述符fd
    5. 緩存IO
  3. Select\Poll\Epoll異步IO
  4. Twsited異步網絡框架

1、事件驅動

事件驅動編程是一種編程範式,這裏程序的執行流由外部事件來決定。它的特色是包含一個事件循環,當外部事件發生時使用回調機制來觸發相應的處理。另外兩種常見的編程範式是(單線程)同步以及多線程編程html

 

當咱們面對以下的環境時,事件驅動模型一般是一個好的選擇: linux

  • 程序中有許多任務
  • 任務之間高度獨立(所以它們不須要互相通訊,或者等待彼此)
  • 在等待事件到來時,某些任務會阻塞。
  • 當應用程序須要在任務間共享可變的數據時,這也是一個不錯的選擇,由於這裏不須要採用同步處理。

網絡應用程序一般都有上述這些特色,這使得它們可以很好的契合事件驅動編程模型。 web

2、IO多路複用

具體介紹見:IO多路複用(番外篇) 編程

討論的背景是Linux環境下的network IO 緩存

IO多路複用(epool)和gevent模塊(協程)區別與聯繫: 安全

都是遇到IO就切換,在linux下底層都是經過libevent.so實現。能夠認爲gevent是對IO多路複用更上層的封裝,IO多路複用是其默認設置,其更專一於任務之間的切換。網絡

1.用戶空間和內核空間

操做系統的核心是內核,能夠訪問受保護的內存空間,也有訪問底層硬件設備的全部權限。爲了保證內核的安全,用戶進程不能直接操做內核(kernel),系統將虛擬空間劃分爲兩部分,一部分爲內核空間,一部分爲用戶空間。 多線程

2.進程切換

爲了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,並恢復之前掛起的某個進程的執行。這種行爲被稱爲進程切換。 併發

3.進程的阻塞

正在執行的進程,因爲期待的某些事件未發生,如請求系統資源失敗、等待某種操做的完成、新數據還沒有到達或無新工做作等,則由系統自動執行阻塞原語(Block),使本身由運行狀態變爲阻塞狀態。可見,進程的阻塞是進程自身的一種主動行爲,也所以只有處於運行態的進程(得到CPU),纔可能將其轉爲阻塞狀態。當進程進入阻塞狀態,是不佔用CPU資源的。 app

4.文件描述符fd

文件描述符(File descriptor)是計算機科學中的一個術語,是一個用於表述指向文件的引用的抽象化概念。文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每個進程所維護的該進程打開文件的記錄表。(文件句柄是真實的文件對象)

注:文件描述符這一律念每每只適用於UNIX、Linux這樣的操做系統。

5.緩存 I/O

又被稱做標準 I/O,大多數文件系統的默認 I/O 操做都是緩存 I/O。在 Linux 的緩存 I/O 機制中,操做系統會將 I/O 的數據緩存在文件系統的頁緩存( page cache )中,也就是說,數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間。

缺點
數據在傳輸過程當中須要在應用程序地址空間和內核進行屢次數據拷貝操做,這些數據拷貝操做所帶來的 CPU 以及內存開銷是很是大的。

6.IO模式

對於一次IO訪問(以read舉例),數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間。(內核態到用戶態數據拷貝)

因此,當一個read操做發生時,它會經歷兩個階段:
1. 等待數據準備 (Waiting for the data to be ready)
2. 將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)

正是由於這兩個階段,linux系統產生了下面五種網絡模式的方案。

  • 阻塞 I/O(blocking IO)
  • 非阻塞 I/O(nonblocking IO)
  • I/O 多路複用( IO multiplexing)
  • 信號驅動 I/O( signal driven IO,不經常使用)
  • 異步 I/O(asynchronous IO,實現複雜用得少)

阻塞IO(bloking IO)

linux下,默認狀況下全部的socket都是blocking。其特色就是在IO執行的兩個階段都是阻塞的 。

非阻塞 I/O(nonblocking IO)

linux下,能夠經過設置socket使其變爲non-blocking。其特色是IO執行的第一個階段不阻塞,用戶進程不斷的主動詢問kernel數據有沒有準備好,kernel作出相應的迴應,但IO執行的第二個階段仍然是阻塞的。

I/O 多路複用( IO multiplexing)

就是咱們說的select,poll,epoll,也稱這種IO方式爲event driven IO。select/epoll的好處就在於單個process就能夠同時處理多個網絡鏈接的IO。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負責的全部socket,當某個socket有數據到達了,就通知用戶進程數據準備好了,但IO執行的第二個階段仍然是阻塞的。

這個圖和blocking IO的圖其實並無太大的不一樣,事實上,其性能還更差一些。由於這裏須要使用兩個system call (select 和 recvfrom),而blocking IO只調用了一個system call (recvfrom)。可是,用select的優點在於它能夠同時處理多個connection。

因此,若是處理的鏈接數不是很高的話,使用select/epoll的web server不必定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。

在IO multiplexing Model中,實際中,對於每個socket,一般都設置成爲non-blocking,可是,如上圖所示,整個用戶的process實際上是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

異步 I/O(asynchronous IO)

linux下的asynchronous IO其實用得不多。

用戶進程發起read操做以後,就能夠開始去作其它的事。從kernel的角度,當它收到一個asynchronous read以後,首先它會馬上返回信號給用戶進程(確認收到請求的信號?),因此不會對用戶進程產生任何block。而後,kernel等待數據準備完成後,將數據拷貝到用戶內存,拷貝完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了。

各個IO Model的比較

3、異步IO(Select\Poll\Epoll)

具體介紹見:Python Select 解析

select目前幾乎在全部的平臺上支持,其良好跨平臺支持也是它的一個優勢,可是使用select單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,不過能夠經過修改宏定義甚至從新編譯內核的方式提高這一限制。select監視到文件描述符有活躍,只向用戶進程返回有活躍信號,沒有返回具體活躍的文件描述符,用戶進程還需再次循環檢查浪費時間和資源。

poll和select在本質上沒有多大差異,可是poll沒有最大文件描述符數量的限制,能夠看做是一個過渡階段。

epoll直到Linux2.6纔出現,它幾乎具有了以前所說的一切優勢,被公認爲Linux2.6下性能最好的多路I/O就緒通知方法。epoll能夠同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變爲就緒狀態,它只說一遍,若是咱們沒有采起行動,那麼它將不會再次告知,這種方式稱爲邊緣觸發),理論上邊緣觸發的性能要更高一些,可是代碼實現至關複雜。

注:epoll在linux上的文件描述符數量,可能會受到OS用戶最大鏈接數限制,注意調整。

1.用select實現socket服務端

服務端

  1. import select  
  2. import socket  
  3. import queue  
  4.     
  5. server = socket.socket()  
  6. server.bind(('localhost',9000))  
  7. server.listen(1000)  
  8.     
  9. server.setblocking(False) #必需要先設置不阻塞  
  10.     
  11. msg_dic = {}  
  12.     
  13. inputs = [server,]  
  14. #inputs = [server,conn] #[conn,]  
  15. #inputs = [server,conn,conn2] #[conn2,]  
  16. outputs = [] #  
  17. #outputs = [r1,] #  
  18. while True:  
  19.     readable ,writeable,exceptional= select.select(inputs, outputs, inputs )  
  20.     print(readable,writeable,exceptional)  
  21.     for r in readable:  
  22.         if r is server: #表明來了一個新鏈接  
  23.             conn,addr = server.accept()  
  24.             print("來了個新鏈接",addr)  
  25.             inputs.append(conn) #是由於這個新創建的鏈接還沒發數據過來,如今就接收的話程序就報錯了,  
  26.             #以要想實現這個客戶端發數據來時server端能知道,就須要讓select再監測這個conn  
  27.             msg_dic[conn] = queue.Queue() #初始化一個隊列,後面存要返回給這個客戶端的數據  
  28.         else#conn2  
  29.             data = r.recv(1024)  
  30.             print("收到數據",data)  
  31.             msg_dic[r].put(data)  
  32.     
  33.             outputs.append(r) #放入返回的鏈接隊列裏  
  34.             # r.send(data)  
  35.             # print("send done....")  
  36.     
  37.     for w in writeable: #要返回給客戶端的鏈接列表  
  38.         data_to_client = msg_dic[w].get()  
  39.         w.send(data_to_client) #返回給客戶端源數據  
  40.     
  41.         outputs.remove(w) #確保下次循環的時候writeable,不返回這個已經處理完的鏈接了  
  42.     
  43.     for e in exceptional:  
  44.         if e in outputs:  
  45.             outputs.remove(e)  
  46.     
  47.         inputs.remove(e)  
  48.     
  49.         del msg_dic[e]  

客戶端

  1. import socket  
  2.     
  3. HOST = 'localhost'  # The remote host  
  4. PORT = 9999  # The same port as used by the server  
  5. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  6. s.connect((HOST, PORT))  
  7. while True:  
  8.     msg = bytes(input(">>:"), encoding="utf8")  
  9.     s.sendall(msg)  
  10.     data = s.recv(1024)  
  11.     
  12.     #  
  13.     print('Received', data)  
  14. s.close() 

2.selectors模塊

封裝好的IO多路複用模塊,默認使用epool,當系統不支持時使用select。

服務端

  1. import selectors  
  2. import socket  
  3.     
  4. sel = selectors.DefaultSelector()  
  5.     
  6.     
  7. def accept(sock, mask):  
  8.     conn, addr = sock.accept()  # Should be ready  
  9.     print('accepted', conn, 'from', addr,mask)  
  10.     conn.setblocking(False)  
  11.     sel.register(conn, selectors.EVENT_READ, read) #新鏈接註冊read回調函數  
  12.     
  13.     
  14. def read(conn, mask):  
  15.     data = conn.recv(1024)  # Should be ready  
  16.     if data:  
  17.         print('echoing', repr(data), 'to', conn)  
  18.         conn.send(data)  # Hope it won't block  
  19.     else:  
  20.         print('closing', conn)  
  21.         sel.unregister(conn)  
  22.         conn.close()  
  23.     
  24.     
  25. sock = socket.socket()  
  26. sock.bind(('localhost', 9999))  
  27. sock.listen(100)  
  28. sock.setblocking(False)  
  29. sel.register(sock, selectors.EVENT_READ, accept)  
  30.     
  31. while True:  
  32.     events = sel.select() #默認阻塞,有活動鏈接就返回活動的鏈接列表  
  33.     for key, mask in events:  
  34.         callback = key.data #accept  
  35.         callback(key.fileobj, mask) #key.fileobj=  文件句柄 

多併發客戶端

  1. import socket  
  2. import sys  
  3.     
  4. messages = [ b'This is the message. ',  
  5.              b'It will be sent ',  
  6.              b'in parts.',  
  7.              ]  
  8. server_address = ('192.168.16.130', 9998)  
  9.     
  10. # Create a TCP/IP socket  
  11. socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(11000)]  
  12. print(socks)  
  13. # Connect the socket to the port where the server is listening  
  14. print('connecting to %s port %s' % server_address)  
  15. for s in socks:  
  16.     s.connect(server_address)  
  17.     
  18. for message in messages:  
  19.     
  20.     # Send messages on both sockets  
  21.     for s in socks:  
  22.         print('%s: sending "%s"' % (s.getsockname(), message) )  
  23.         s.send(message)  
  24.     
  25.     # Read responses on both sockets  
  26.     for s in socks:  
  27.         data = s.recv(1024)  
  28.         print'%s: received "%s"' % (s.getsockname(), data) )  
  29.         if not data:  
  30.             print'closing socket', s.getsockname() )  

4、Twsited異步網絡框架

Twisted本身用異步形式重寫了SSH、DNS、FTP、HTTP等,代碼比較複雜,遊戲開發會用到。

http://www.cnblogs.com/alex3714/articles/5248247.html

參考:

http://www.cnblogs.com/alex3714/articles/5248247.html

http://www.cnblogs.com/alex3714/articles/5876749.html

http://www.cnblogs.com/alex3714/p/4372426.html

相關文章
相關標籤/搜索