阻塞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中,用戶進程實際上是須要不斷的主動詢問kernel數據準備好了沒有ide
非阻塞如何利用函數
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
把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'))
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
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"))