1. 程式人生 > >python基礎之IO模型

python基礎之IO模型

需要 ech 列表 window 網絡連接 其它 ron 都是 listen

IO模型分類

五種IO Model

blocking IO      阻塞IO

nonblocking IO     非阻塞IO

IO multiplexing     IO多路復用

signal driven IO    信號驅動IO

asynchronous IO    異步IO

signal driven IO(信號驅動IO)在實際中並不常用,所以只剩下四種IO Model。

網絡IO的兩個過程

對於一個network IO ,會涉及到兩個系統對象,一個是調用這個IO的process (or thread),另一個就是系統內核(kernel)。當一個read操作發生時,它會經歷兩個階段:

  • 等待數據準備 (Waiting for the data to be ready):等待系統接收數據
  • 將數據從內核拷貝到進程中 (Copying the data from the kernel to the process):進程從系統緩存中拿到數據

同步IO:在這兩個過程中有任意階段出現阻塞狀態。

  阻塞IO、非阻塞IO、IO多路復用都是同步IO

異步IO:全程無阻塞的IO

  異步IO屬於異步IO(真的沒毛病)

阻塞IO(Blocking IO)

技術分享

UDP包:當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:準備數據。對於network io來說,很多時候數據在一開始還沒有到達(比如,還沒有收到一個完整的UDP包),這個時候kernel就要等待足夠的數據到來。而在用戶進程這邊,整個進程會被阻塞。當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,然後kernel返回結果,用戶進程才解除block的狀態,重新運行起來。

blocking IO的特點就是在IO執行的兩個階段都被block了。

示例:

 1 #服務端
 2 import socket
 3 sock=socket.socket()   #默認是TCP
 4 sock.bind(("127.0.0.1",8088))
 5 
 6 sock.listen(5)
 7 while True:
 8     conn,addr=sock.accept()   #默認是就是阻塞的方式,監聽等待客戶端連接(階段一):等待中的阻塞
 9                               #客戶端連接後接收數據(階段二):socket對象和客戶端地址,雖然接收數據的過程很快但是實際上也是阻塞
10     while True:
11         data=conn.recv(1024)    #也是兩個階段的阻塞
12         print(data.decode(‘utf8‘))
13         if data.decode(‘utf8‘) ==‘q‘:
14             break
15         respnse=input(‘>>>>‘)
16         conn.send(respnse.encode(‘utf8‘))
17 
18 
19 #客戶端
20 import socket
21 sock=socket.socket()
22 sock.connect(("127.0.0.1",8088))
23 
24 while True:
25     data=input(‘>>>‘).strip()
26     sock.send(data.encode(‘utf8‘))
27     s_data = sock.recv(1024)    #兩個階段的阻塞
28     print(s_data.decode(‘utf8‘))

非阻塞IO(Non-blocking IO)

技術分享

當用戶進程發出read操作時,如果kernel中的數據還沒有準備好,那麽它並不會block用戶進程,而是立刻返回一個error。所以用戶進程不需要等待,而是馬上就得到了一個結果,用戶進程判斷結果是一個error時,它就知道數據還沒有準備好,於是它可以再次發送read操作。一旦kernel中的數據準備好了,並且又再次收到了用戶進程的system call,那麽它馬上就將數據拷貝到了用戶內存,然後返回。這個過程中,用戶進程是需要不斷的主動詢問kernel數據好了沒有。

非阻塞實際上是將大的整片時間的阻塞分成N多的小的阻塞,每次recvform系統調用之間,可以幹點別的事情,然後再發起recvform系統調用,重復的過程通常被稱之為輪詢。輪詢檢查內核數據,直到數據準備好,再拷貝數據到進程,進行數據處理。需要註意,拷貝數據整個過程,進程仍然是屬於阻塞的狀態。

優點:能夠在等待任務完成的時間裏幹其他活了(包括提交其他任務,也就是 “後臺” 可以有多個任務在同時執行)。

缺點:任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次read操作,而任務可能在兩次輪詢之間的任意時間完成。這會導致整體數據吞吐量的降低。

 1 #服務端
 2 
 3 import socket
 4 import time
 5 sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)   #默認是TCP
 6 sock.bind(("127.0.0.1",8088))
 7 sock.listen(5)
 8 sock.setblocking(False)
 9 
10 while True:
11     try:
12         print(‘server waiting‘)
13         conn, addr = sock.accept()  # 默認是個阻塞的方式,等待客戶端連接
14         while True:
15             data = conn.recv(1024)  #這邊也是阻塞的IO
16             print(data.decode(‘utf8‘))
17             if data.decode(‘utf8‘) == ‘q‘:
18                 break
19             respnse = input(‘>>>>‘)
20             conn.send(respnse.encode(‘utf8‘))
21     except Exception as e:
22         print (e)
23         time.sleep(4)
24 
25 #客戶端
26 import socket
27 sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)   #默認是TCP
28 
29 while True:
30     sock.connect(("127.0.0.1", 8088))   #因為服務端recv也是非阻塞,所以要不斷重新連接
31     data=input(‘>>>‘).strip()
32     sock.send(data.encode(‘utf8‘))
33     s_data = sock.recv(1024)
34     print(s_data.decode(‘utf8‘))

IO多路復用IO multiplexing

IO多路復用,也叫做event driven IO,實現方式:select,poll或epoll

IO多路復用的好處就在於單個process就可以同時處理多個網絡連接的IO

技術分享

用戶進程調用了select,那麽整個進程會被block,而同時,kernel會“監視”所有select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操作,將數據從kernel拷貝到用戶進程。這個過程中有兩次system call(系統調用) select阻塞時候 和 recvfrom阻塞時候。用多路復用的的優勢在於它可以同時處理大批量的connection,不適用單個或少量,少量還不如multi-threading + blocking IO。

select示例:

 1 #服務端
 2 import socket
 3 import select
 4 sock=socket.socket()
 5 sock.bind(("127.0.0.1",8088))
 6 sock.listen(5)
 7 
 8 inp=[sock,] #定義監聽的套接字對象列表,列表列表裏可以有多個對象
 9 
10 while True:
11     #字段順序:input list 、output list、error list、date(可以不寫)
12     r=select.select(inp,[],[],None) #對比的是sock.accept(),這一步只做了監聽的事情,監聽哪個socket對象活動,當沒有客戶端連接時候會阻塞
13     # 當監聽到有活動的socket對象時候,將返回值給r
14     print(‘r‘,r)
15     print(‘r‘,r[0])
16     #r接收的返回是一個元組,r[0]是活動的對象列表
17 
18     for obj in r[0]:
19         if obj == sock: #如果活動的對象是sock,那麽將客戶端對象加入監聽列表,客戶端再發數據時候,觸發客戶端的對象活動
20             conn,addr=obj.accept()  #accept只做第二個階段的事情,取回數據:client的socket對象和地址
21             print(conn,addr)
22             inp.append(conn)
23         else:
24             data=obj.recv(1024)
25             print(data.decode(‘utf8‘))
26             resp=input(‘>>>‘)
27             obj.send(resp.encode(‘utf8‘))
28 
29 #客戶端
30 import socket
31 sock=socket.socket()  
32 sock.connect(("127.0.0.1", 8088))
33 while True:
34     data=input(‘>>>‘).strip()
35     sock.send(data.encode(‘utf8‘))
36     s_data = sock.recv(1024)
37     print(s_data.decode(‘utf8‘))

因為使用的是for循環,當多個客戶端發消息給服務端,只能一個個順序處理。

在windows下只能用select實現多路復用

在Linux可以使用select、poll、epoll實現,推薦使用epoll,對比:

  select和poll的監聽方式為輪詢方式,即每次都要循環一遍監聽列表,效率低,另外select有連接數限制,poll無限

  epoll連接數無限,區別在於監聽方式不同,每個socket對象綁定一個回調函數,當socket對象活動了就觸發回調函數,把自己寫到活動列表中,epoll直接調用活動列表

信號驅動IO(signal driven IO)

不常用,不做說明

異步IO(Asynchronous I/O)

技術分享

用戶進程發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻返回,所以不會對用戶進程產生任何block。然後,kernel會等待數據準備完成,然後將數據拷貝到用戶內存,當這一切都完成之後,kernel會給用戶進程發送一個signal,告訴它read操作完成了。

IO模型區別

技術分享

selectors模塊

該模塊能夠按照系統平臺,自動選擇多路復用的方式。

 1 #服務端
 2 import selectors
 3 import socket
 4 
 5 sel=selectors.DefaultSelector()
 6 
 7 def accept(sock,mask):
 8     conn,addr=sock.accept()     #4、獲取客戶端的conn對象和地址
 9     print(‘accetped‘,conn,‘from‘,addr)
10     conn.setblocking(False)
11     sel.register(conn,selectors.EVENT_READ,read)    #5、註冊conn對象,將conn對象和函數read綁定
12 
13 def read(conn,mask):
14     data=conn.recv(1024)    #9、服務端通過conn對象接收消息,進行下面的邏輯處理
15     if data:
16         print(‘echoing‘,repr(data),‘to‘,conn)
17         conn.send(data)
18     else:
19         print(‘closing‘,conn)
20         sel.unregister(conn)
21         conn.close()
22 
23 sock=socket.socket()
24 sock.bind((‘127.0.0.1‘,8088))
25 sock.listen(100)
26 sock.setblocking(False)
27 sel.register(sock,selectors.EVENT_READ,accept)  #sock對象註冊綁定accept函數
28 
29 while True:
30     #不管是哪個方式,都是使用select方法監聽活動的socket對象
31     events=sel.select()     #1、執行sel阻塞監聽,當有客戶端連接,激活sock對象,返回一個存放活動sock對象相關信息的列表
32                             #6、客戶端通過conn對象發送消息,激活sel監聽列表中的的conn對象,返回一個存放活動conn對象相關信息的列表
33     print(events,type(events))
34     for key,mask in events:
35         print(mask)
36         print(key.data)   #socket對象註冊綁定的accept函數
37         print(key.fileobj)
38         callback=key.data   #2、取得返回的sock綁定的函數
39                             #7、取得返回conn綁定的函數
40         callback(key.fileobj,mask)  #3、key.fileobj是sock對象,執行函數
41                                     #8、執行函數read,並傳入conn對象
42 
43 
44 #客戶端
45 import socket
46 sock=socket.socket()
47 sock.connect(("127.0.0.1", 8088))
48 while True:
49     data=input(‘>>>‘).strip()
50     sock.send(data.encode(‘utf8‘))
51     s_data = sock.recv(1024)
52     print(s_data.decode(‘utf8‘))

python基礎之IO模型