python入門第三十五天--事件驅動模型(補)練習理解

阻塞IO

阻塞IO(blocking IO)的特色:就是在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被block了。python

 什麼是阻塞呢?想象這種情形,好比你等快遞,但快遞一直沒來,你會怎麼作?有兩種方式:服務器

  • 快遞沒來,我能夠先去睡覺,而後快遞來了給我打電話叫我去取就好了。
  • 快遞沒來,我就不停的給快遞打電話說:擦,怎麼還沒來,給老子快點,直到快遞來。

很顯然,你沒法忍受第二種方式,不只耽擱本身的時間,也會讓快遞很想打你。
而在計算機世界,這兩種情形就對應阻塞和非阻塞忙輪詢。多線程

    • 非阻塞忙輪詢:數據沒來,進程就不停的去檢測數據,直到數據來。
    • 阻塞:數據沒來,啥都不作,直到數據來了,才進行下一步的處理

 

server併發

 1 #!/usr/bin/env python3
 2 #-*- coding:utf-8 -*-
 3 '''
 4 Administrator 
 5 2018/9/3 
 6 '''
 7 import socket
 8 
 9 sk=socket.socket()
10 sk.bind(('127.0.0.1',8080))
11 sk.listen(3)
12 
13 while 1:
14     conn,addr=sk.accept()
15     while 1:
16         data=conn.recv(1024)
17         print(data.decode('utf8'))
18         conn.sendall(data)

clientapp

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
'''
Administrator 
2018/9/3 
'''
import socket

sk=socket.socket()
sk.connect(('127.0.0.1',8080))

while 1:
    inp=input(">>>")
    sk.sendall(inp.encode('utf8'))
    data=sk.recv(1024)
    print(data.decode('utf8'))

運行結果:socket

"D:\Program Files (x86)\python36\python.exe" F:/python從入門到放棄/9.3/client.py
>>>hello
hello
>>>nihao
nihao
>>>中國
中國
>>>亞運會
亞運會
>>>中國金牌數
中國金牌數
>>>

 

非阻塞型IO模型

非阻塞式IO中,用戶進程實際上是須要不斷的主動詢問kernel數據準備好了沒有ide

非阻塞如何利用函數

  • 吃滿 CPU !
  • 寧肯用 while True ,也不要阻塞發呆!
  • 只要資源沒到,就先作別的事!

 

server:spa

 

 1 #!/usr/bin/env python3
 2 #-*- coding:utf-8 -*-
 3 '''
 4 Administrator 
 5 2018/9/3 
 6 '''
 7 import socket,time
 8 #非阻塞型IO模型
 9 sk=socket.socket()
10 sk.bind(('127.0.0.1',8080))
11 sk.listen(3)
12 
13 sk.setblocking(False)#設置非阻塞。 IO模型發生了變化
14 print("start listen")
15 conn_list=[]#鏈接列表
16 while 1:
17     try:
18         conn, addr = sk.accept()
19         print("connet by",addr)
20         conn_list.append(conn)
21         conn.setblocking(False)#設置非阻塞。 IO模型發生了變化
22         # while 1:
23         #     data=conn.recv(1024)
24         #     print(data.decode('utf8'))
25         #     conn.sendall(data)
26     except Exception as e:
27         print("沒有接收到數據。警告:%s"%e)
28         time.sleep(3)
29 
30     for conn in conn_list:
31         try:
32             data=conn.recv(1024)
33             if data:
34                 print(data.decode('utf8'))
35                 conn.sendall(data)
36             else:
37                 print('close conn',conn)
38                 conn.close()
39                 conn_list.remove(conn)
40                 print("還有在線的客戶端個數:%s"%len(conn_list))
41         except IOError:
42             pass

client:操作系統

 

 1 #!/usr/bin/env python3
 2 #-*- coding:utf-8 -*-
 3 '''
 4 Administrator 
 5 2018/9/3 
 6 '''
 7 import socket
 8 
 9 sk=socket.socket()
10 
11 sk.connect(('127.0.0.1', 8080))
12 while 1:
13     inp=input(">>>")
14     if inp != 'q':
15         sk.sendall(inp.encode('utf8'))
16         data=sk.recv(1024)
17         print("返回的數據%s"%data.decode('utf8'))
18     else:
19         sk.close()
20         print("關閉客戶端")
21         break#跳出循環

運行結果:

"D:\Program Files (x86)\python36\python.exe" F:/python從入門到放棄/9.3/server.py
start listen
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
connet by ('127.0.0.1', 54241)
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
connet by ('127.0.0.1', 54242)
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
connet by ('127.0.0.1', 54243)
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
這是1個
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
這是2個
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
這是3個
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
close conn <socket.socket fd=216, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 54241)>
還有在線的客戶端個數:2
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
close conn <socket.socket fd=232, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 54242)>
還有在線的客戶端個數:1
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。
close conn <socket.socket fd=248, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 54243)>
還有在線的客戶端個數:0
沒有接收到數據。警告:[WinError 10035] 沒法當即完成一個非阻止性套接字操做。

Process finished with exit code 1

 

多路複用IO

把socket交給操做系統去監控,至關於找個代理人(select), 去收快遞。快遞到了,就通知用戶,用戶本身去取。

阻塞I/O只能阻塞一個I/O操做,而I/O複用模型可以阻塞多個I/O操做,因此才叫作多路複用

使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操做,感受效率更差。

可是,使用select之後最大的優點是用戶能夠在一個線程內同時處理多個socket的IO請求。用戶能夠註冊多個socket,而後不斷地調用select讀取被激活的socket,

便可達到在同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須經過多線程的方式才能達到這個目的。

epoll是目前Linux上效率最高的IO多路複用技術。

epoll是惰性的事件回調,惰性事件回調是由用戶進程本身調用的,操做系統只起到通知的做用。

epoll實現併發服務器,處理多個客戶端



 


 1 import socket
 2 import select
 3 
 4 sk1=socket.socket()
 5 sk1.bind(('127.0.0.1',8080))
 6 sk1.listen(3)
 7 
 8 sk2=socket.socket()
 9 sk2.bind(('127.0.0.1',8081))
10 sk2.listen(3)
11 while 1:
12     r,w,e=select.select([sk1,sk2],[],[])#監聽--------讀 ,寫 ,錯誤
13     for obj in r:
14         conn,address=obj.accept()
15         # obj.recv(1024)
16         conn.send("i am server...".encode('utf8'))
server
 1 import socket
 2 import time
 3 
 4 sk=socket.socket()
 5 
 6 
 7 while 1:
 8     sk.connect(('127.0.0.1', 6667))
 9     print("ok")
10     sk.sendall(bytes("hello","utf8"))
11     time.sleep(2)
12     break
client

 1 import socket
 2 import select
 3 sk=socket.socket()
 4 sk.bind(("127.0.0.1",8800))
 5 sk.listen(5)
 6 
 7 sk1=socket.socket()
 8 sk1.bind(("127.0.0.1",6667))
 9 sk1.listen(5)
10 
11 while True:
12     r,w,e=select.select([sk,sk1],[],[],5)
13     for i in r:#r 接受到的是綁定的兩個socket 對象之一。是服務端的對象  conn 是這兩個r對象鏈接的客戶端的對象
14         # conn,add=i.accept()
15         # print(conn)
16         print("hello")
17     print('>>>>>>',r)
 1 import socket
 2 import time
 3 
 4 sk=socket.socket()
 5 
 6 
 7 while 1:
 8     sk.connect(('127.0.0.1', 6667))
 9     print("ok")
10     sk.sendall(bytes("hello","utf8"))
11     time.sleep(2)
12     break

運行結果:

...........重複打印.........
hello
>>>>>> [<socket.socket fd=216, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6667)>]
hello
>>>>>> [<socket.socket fd=216, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6667)>]
hello
>>>>>> [<socket.socket fd=216, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6667)>]

...................

 

select屬於水平觸發
觸發方式:水平觸發,邊緣觸發。
水平狀態,有 就觸發,沒有就不觸發 1觸發 0不觸發
邊緣觸發,有變化就觸發,沒有變化就不觸發。0->1觸發 1->0觸發




 1 #!/usr/bin/env python3
 2 #-*- coding:utf-8 -*-
 3 '''
 4 Administrator 
 5 2018/9/3 
 6 '''
 7 import socket
 8 import select
 9 
10 sk=socket.socket()
11 sk.bind(('127.0.0.1',8800))
12 sk.listen(5)
13 
14 
15 inp=[sk,]
16 while 1:
17     inputs,outputs,errors=select.select(inp,[],[],5)
18     for obj in inputs:
19         if obj==sk:
20             conn,addr=obj.accept()
21             print(conn)
22             inp.append(conn)
23             # data=conn.recv(1024)
24             # print(data.decode('utf8'))
25             # conn.sendall(data)
26         else:
27             data = obj.recv(1024)
28             print(data.decode('utf8'))
29             inps=input("回答%s>>>"%inp.index(obj))
30             obj.sendall(inps.encode("utf8"))

客戶端:

 1 #!/usr/bin/env python3
 2 #-*- coding:utf-8 -*-
 3 '''
 4 Administrator 
 5 2018/9/3 
 6 '''
 7 import socket
 8 import time
 9 
10 sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
11 
12 sk.connect(('127.0.0.1', 8800))
13 while 1:
14     inp=input(">>>").strip()
15     sk.sendall(bytes(inp,"utf8"))
16     data=sk.recv(1024)
17     print(str(data,"utf8"))


改進::: 當有用戶退出時,不影響服務端的聊天通訊::

 

 1 #!/usr/bin/env python3
 2 #-*- coding:utf-8 -*-
 3 '''
 4 Administrator 
 5 2018/9/3 
 6 '''
 7 import socket
 8 import select
 9 
10 sk=socket.socket()
11 sk.bind(('127.0.0.1',8800))
12 sk.listen(5)
13 
14 
15 inp=[sk,]
16 while 1:
17     inputs,outputs,errors=select.select(inp,[],[],5)
18     for obj in inputs:
19         if obj==sk:
20             conn,addr=obj.accept()
21             print(conn)
22             inp.append(conn)
23             # data=conn.recv(1024)
24             # print(data.decode('utf8'))
25             # conn.sendall(data)
26         else:
27             try:
28                 data = obj.recv(1024)#若是斷開聯繫,則把該對象從inp列表中刪除
29             except Exception:
30                 inp.remove(obj)
31             if obj in inp:#篩選一下
32                 print(data.decode('utf8'))
33                 inps=input("回答%s>>>"%inp.index(obj))
34                 obj.sendall(inps.encode("utf8"))
相關文章
相關標籤/搜索