python非阻塞输入_python_非阻塞套接字及I/O流
首先,我們要明確2個問題:
普通套接字實現(xiàn)的服務(wù)端有什么缺陷嗎?
有,一次只能服務(wù)一個客戶端!
這種缺陷是如何造成的?
accept阻塞:當沒有套接字連接請求過來的時候會一直等待著
recv阻塞:當連接的這個客戶端沒有發(fā)數(shù)據(jù)過來的時候,也會一直等待著
importsocket
server=socket.socket()
server.bind(('127.0.0.1', 8888))
server.listen(5)print("執(zhí)行到這, 上面沒問題了")whileTrue:
conn, addr= server.accept() #阻塞
print(conn, addr)print("{}連接".format(addr))whileTrue:
data= conn.recv(1024) #阻塞
print(data)if notdata:break
當前I/O流
那么非阻塞套接字和普通套接字的區(qū)別?
非阻塞套接字在accept或recv的時候不會發(fā)生阻塞,要么成功,要么失敗拋出BlockingIOError異常
非阻塞IO模型
非阻塞套接字實現(xiàn)并發(fā)
并發(fā)是什么?
在一個時間段,完成某件事,就是并發(fā)
對立的概念,什么是并行?
同時發(fā)生,不管有幾件事,同時進行,就是并行
非阻塞套接字如何實現(xiàn)并發(fā)服務(wù)端?
配合try語句,將代碼順序重排,避開阻塞
實現(xiàn)并發(fā)服務(wù)多個客戶端 !
那么現(xiàn)在用非阻塞套接字完善上一章博客的代碼:
服務(wù)端:
importsocketimporttime#并發(fā)操作
server= socket.socket() #創(chuàng)建一個socket
server.setblocking(False) # 設(shè)置成非阻塞
server.bind(('0.0.0.0', 8888))
server.listen()print("執(zhí)行到這, 上面沒問題了")
all_connction= [] #用來存放和連接客戶端通信的套接字
whileTrue:#處理用戶的連接
try:
conn, addr= server.accept() #阻塞
conn.setblocking(False) # 設(shè)置成非阻塞
print(conn, addr)print("{}連接".format(addr))
all_connction.append(conn)exceptBlockingIOError as e:pass
#處理已經(jīng)連接的客戶的消息
time.sleep(1)
new_li=all_connction.copy()for conn innew_li:try:
data= conn.recv(1024) #阻塞
if data == b'':
all_connction.remove(conn)
conn.close()else:print("接收到的數(shù)據(jù):", data.decode())
conn.send(data)exceptBlockingIOError as e:passserver.close()
客戶端:
#客戶端Linux、window系統(tǒng)下:輸入命令通過服務(wù)端返回
importsocket#聲明協(xié)議類型,同時生成socket連接對象
client =socket.socket()#鏈接地址和端口,元組(本地,端口)
client.connect(('127.0.0.1', 8888))#使用input循環(huán)向服務(wù)端發(fā)送請求
whileTrue:
msg= input("-->>:").strip()if len(msg) ==0:continue
#發(fā)送數(shù)據(jù) b將字符串轉(zhuǎn)為bys類型
client.send(msg.encode("utf-8"))#接收服務(wù)器端的返回,需要聲明收多少,默認1024字節(jié)
id = 1024data=client.recv(id).decode()#打印data是recv的data
print("recv: %s" %data)#關(guān)閉接口
client.close()
IO多路復(fù)用
IO多路復(fù)用也是阻塞IO, 只是阻塞的方法是select/poll/epoll, 好處就是單個進程可以處理多個socket
用select,poll,epoll監(jiān)聽多個io對象,當io對象有變化(有數(shù)據(jù))的時候,則立即通知相應(yīng)程序進行讀或者寫操作。
但select,poll,epoll本質(zhì)上都是同步I/O,因為他們都需要在讀寫時間就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的
因為阻塞I/O只能阻塞一個I/O操作,而I/O復(fù)用模型能夠阻塞多個I/O操作, 所以才叫做多路復(fù)用
非阻塞套接字實現(xiàn)的服務(wù)端還有什么不完美的地方嗎?
關(guān)鍵一: 任何操作都是要花CPU資源的!
關(guān)鍵二: 如果數(shù)據(jù)還沒有到達。那么accept, recv操作都是在做無用功!
關(guān)鍵三: 對異常BlockIOError的處理也是在做無用功!
總結(jié):不完美的CPU利用率
I/O多路復(fù)用模型
epoll? 目前Linux上效率最高的IO多路復(fù)用技術(shù)!
epoll 基于惰性的事件回調(diào)機制
惰性的事件回調(diào)是由用戶自己調(diào)用的,操作系統(tǒng)只起到通知的作用
使用步驟
? ? 導(dǎo)入IO多路復(fù)用選擇器
注冊事件和回調(diào)
查詢
? ? ?回調(diào)
應(yīng)用實例:
import selectors #提供IO多路技術(shù)給我們使用的
importsocketimporttime
server= socket.socket() #創(chuàng)建一個套接字
server.bind(('0.0.0.0', 8888))
server.listen()
epoll= selectors.EpollSelector() #生成一個epoll
defnewFunc(conn):
data= conn.recv(1024)if data == b'':
epoll.unregister(conn)#取消注冊
conn.colse()else:print("收到的數(shù)據(jù):",data.decode())
conn.send(data)deffunc(ser):
conn, addr=ser.accept()print("處理了連接")#查看是否有數(shù)據(jù)
epoll.register(conn, selectors.EVENT_READ, newFunc)
time.sleep(1)
epoll.register(server, selectors.EVENT_READ, func)#注冊 給他3個參數(shù) 1.想看什么,看的是server 2.有沒有數(shù)據(jù)連接(EVENT_READ可讀,EVENT_WRITE可寫) 3.回調(diào)函數(shù)
whileTrue:
events= epoll.select() #查詢
for key, mask inevents:
callback= key.data #函數(shù)的名字
sock = key.fileobj #套接字
callback(sock)#函數(shù)調(diào)用
importsocket
client=socket.socket()
client.connect(('127.0.0.1', 8888))whileTrue:
data= input("請輸入要發(fā)送的數(shù)據(jù):")
client.send(data.encode())print("接收到的數(shù)據(jù):", client.recv(1024).decode())
下面是一些理論的東西,大家有時間可以讀一遍。
## 什么是IO操作
? IO在計算機中指Input/Output,也就是輸入和輸出 。由于程序和運行時數(shù)據(jù)是放在內(nèi)存中的,由CPU來執(zhí)行的,涉及到數(shù)據(jù)交換的地方,通常是磁盤、網(wǎng)絡(luò)等,就需要IO接口。
? 比如你打開瀏覽器,訪問百度,瀏覽器需要通過網(wǎng)絡(luò)獲取百度的網(wǎng)頁數(shù)據(jù)。瀏覽器首先發(fā)送數(shù)據(jù)給百度的服務(wù)器,告訴它我要訪問它,這個過程是往外發(fā)數(shù)據(jù),就做Output。然后百度服務(wù)器在把網(wǎng)頁數(shù)據(jù)發(fā)過來。這個過程是從外面接收數(shù)據(jù),就做input
? 所以,通常,程序完成IO操作會有Input和Output 這兩個過程, 但是也可能只有一個,比如打開一個文件。就只是從磁盤讀取文件到內(nèi)存,就只有Input操作 ,反過來,向文件中寫入數(shù)據(jù),就只是一個Output操作。
## 1 流的概念
? IO編程中,Stream(流)是一個很重要的概念,可以把流想象成一個水管,數(shù)據(jù)就是水管里的水,但是只能單向流動。Input Stream就是數(shù)據(jù)從外面(磁盤、網(wǎng)絡(luò))流進內(nèi)存,Output Stream就是數(shù)據(jù)從內(nèi)存流到外面去。對于瀏覽網(wǎng)頁來說,瀏覽器和百度服務(wù)器之間至少需要建立兩根水管,才可以既能發(fā)數(shù)據(jù),又能收數(shù)據(jù)。
? 由于CPU和內(nèi)存的速度遠遠高于外設(shè)的速度,所以,在IO編程中,就存在速度嚴重不匹配的問題。 可能存在這樣的情況:讀取數(shù)據(jù)的時候,流中還沒有數(shù)據(jù);寫入數(shù)據(jù)的時候,流中數(shù)據(jù)已經(jīng)滿了,沒有空間寫入了。
? 舉個例子,socket通信, 通過recv讀取另一方發(fā)過來的數(shù)據(jù),但是對方還沒把數(shù)據(jù)準備好發(fā)過來。此時有兩種處理辦法:
- 阻塞,等待數(shù)據(jù)準備好了,再讀取出來返回;
- 非阻塞,通過輪詢的方式,查詢是否有數(shù)據(jù)可以讀取,直到把數(shù)據(jù)讀取返回。
## 2. 同步,異步,阻塞, 非阻塞的概念
在IO操作過程中,可能會涉及到同步(synchronous)、異步(asynchronous)、阻塞(blocking)、非阻塞(non-blocking)、IO多路復(fù)用(IO multiplexing)等概念。他們之間的區(qū)別是什么呢?
以socket為例子,在socket通信過程中,涉及到兩個對象:
1. 調(diào)用這個IO的進程(process)或線程(thread)
2. 操作系統(tǒng)內(nèi)核(kernel)
比如服務(wù)端調(diào)用recv來接收客戶端的數(shù)據(jù),會涉及兩個過程:
1. 等待數(shù)據(jù)準備好(Waiting for the data to be ready),也就是客戶端要通過網(wǎng)絡(luò)把數(shù)據(jù)發(fā)給服務(wù)端;
2. 客戶端把數(shù)據(jù)發(fā)送過來,首先會被操作系統(tǒng)內(nèi)核接收到,程序里面需要使用這個數(shù)據(jù),要將數(shù)據(jù)從內(nèi)核中拷貝到進程中( Copying the data from the kernel to the process))
根據(jù)這兩個階段中,不同階段是否發(fā)生阻塞,將產(chǎn)生不同的效果。
### 阻塞 VS 非阻塞
阻塞IO:
- 在1、2階段都發(fā)生阻塞;
- 調(diào)用阻塞IO會一直block住進程,直到操作完成
非阻塞IO:
- 在第1階段沒有阻塞,在第2階段發(fā)生阻塞;
- 當用戶進程發(fā)出IO請求時, 如果內(nèi)核中的數(shù)據(jù)還沒由準備好,那么它并不會block用戶進程,而是立即返回一個錯誤, 在程序看來,它發(fā)起一個請求后,并不需要等待,而是馬上就得到一個結(jié)果。
- 非阻塞IO需要不斷輪詢,查看數(shù)據(jù)是否已經(jīng)準備好了;
阻塞與非阻塞可以簡單理解為調(diào)用一個IO操作能不能立即得到返回應(yīng)答,如果不能立即獲得返回,需要等待,那就阻塞了;否則就可以理解為非阻塞 。
### **同步VS異步**
同步與異步是針對應(yīng)用程序與內(nèi)核的交互而言的
同步:第二步數(shù)據(jù)從內(nèi)核緩存寫入用戶緩存一定是由用戶線程自行讀取數(shù)據(jù),處理數(shù)據(jù)。
異步:第二步數(shù)據(jù)是內(nèi)核寫入的,并放在了用戶線程指定的緩存區(qū),寫入完畢后通知用戶線程。
?同步和異步針對應(yīng)用程序來,關(guān)注的是程序中間的協(xié)作關(guān)系;阻塞與非阻塞更關(guān)注的是單個進程的執(zhí)行狀態(tài)。
?同步有阻塞和非阻塞之分,異步?jīng)]有,它一定是非阻塞的。
?阻塞、非阻塞、多路IO復(fù)用,都是同步IO,異步必定是非阻塞的,所以不存在異步阻塞和異步非阻塞的說法。真正的異步IO需要CPU的深度參與。換句話說,只有用戶線程在操作IO的時候根本不去考慮IO的執(zhí)行全部都交給CPU去完成,而自己只等待一個完成信號的時候,才是真正的異步IO。所以,拉一個子線程去輪詢、去死循環(huán),或者使用select、poll、epool,都不是異步。
?同步:執(zhí)行一個操作之后,進程觸發(fā)IO操作并等待(也就是我們說的阻塞)或者輪詢的去查看IO操作(也就是我們說的非阻塞)是否完成,等待結(jié)果,然后才繼續(xù)執(zhí)行后續(xù)的操作。
?異步:執(zhí)行一個操作后,可以去執(zhí)行其他的操作,然后等待通知再回來執(zhí)行剛才沒執(zhí)行完的操作。
?阻塞:進程給CPU傳達一個任務(wù)之后,一直等待CPU處理完成,然后才執(zhí)行后面的操作。
?非阻塞:進程給CPU傳達任務(wù)后,繼續(xù)處理后續(xù)的操作,隔斷時間再來詢問之前的操作是否完成。這樣的過程其實也叫輪詢。
## 3. IO多路復(fù)用
?I/O多路復(fù)用也是阻塞IO,只是阻塞的方法是select/poll/epoll ,好處就是單個進程可以處理多個socket
用select, poll, epoll監(jiān)聽多個io對象,當io對象有變化(有數(shù)據(jù))的時候,則立即通知相應(yīng)程序進行讀或?qū)懖僮鳌5玸elect,poll,epoll本質(zhì)上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的
因為阻塞I/O只能阻塞一個I/O操作,而I/O復(fù)用模型能夠阻塞多個I/O操作,所以才叫做多路復(fù)用。
到這里整理完畢。
博客鏈接:https://www.cnblogs.com/lixy-88428977
聲明:本文為博主學習感悟總結(jié),水平有限,如果不當,歡迎指正。如果您認為還不錯,歡迎轉(zhuǎn)載。轉(zhuǎn)載與引用請注明作者及出處。
總結(jié)
以上是生活随笔為你收集整理的python非阻塞输入_python_非阻塞套接字及I/O流的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python希尔排序的优缺点_Pytho
- 下一篇: 什么时候出python4_Python4