IO多路複用的三種實現方法
IO多路複用
定義:同時監控多個IO事件,當哪個IO事件準備就緒就執行哪個IO事件。
以此形成可以同時操作多個IO的併發行為,從而避免一個IO阻塞,造成所有IO都無法執行
IO事件準備就緒:是一種IO必然要發生的臨界狀態。
IO多路複用的程式設計實現
1.將IO設定為關注IO(註冊IO)
2.將關注IO提交給核心監測
3.處理核心給我們反饋的準備就緒的IO
具體實現方案: 3種
1.select ----->windows linux unix(蘋果核心)
2.poll ----->linux unix
3.epoll ----->linux unix
三種方法在select 模組中
import select
select(rlist, wlist, xlist[, timeout]) ----> (rlist, wlist, xlist)三個返回值
rs,ws,xs=select(rlist, wlist, xlist[, timeout])四個引數三個返回值
功能:用來監控IO事件,阻塞等待IO事件發生
引數:rlist 列表 存放我們監控等待處理的IO事件(不受自己控制的)
wlist 列表 存放我們要主動操作的IO事件
xlist 列表 我們要關注出錯處理的IO事件
timeout 超時時間,超過一定時間則不阻塞
返回值: rs 列表 rlist 中準備就緒的IO
ws 列表 wlist 中準備就緒的IO
xs 列表 xlist 中準備就緒的IO
注意: 1.wlist中如果有IO事件則select立即會返回為ws
2.在處理IO過程中不要處理一個客戶端長期佔有服務端使服務端無法執行到select的情況
3.IO多路複用佔用計算機資源少,io效率高
程式碼:
from select import select
from socket import *
#建立套接字作為我們關注的IO
s=socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind((‘0.0.0.0’,8888))
s.listen(5)
rlist=[s]
wlist=[]
xlist=[s]
#這些IO實時進行監控
while True:
#提交監測我們關注的IO,阻塞等待IO發生
rs,ws,xs=select(rlist,wlist,xlist)
for r in rs:
if r is s:
#連結是持續的不會斷開
c,addr=r.accept()
print(‘Connect from’,addr)
#將物件新增到關注列表
rlist.append©
else:
data=r.recv(1024)
if not data:
rlist.remove®
r.close()
else:
print(data.decode())
#將客戶端套接字放入wlist列表
wlist.append®
for w in ws:
w.send(b'Receive your message')
wlist.remove(w)
for x in xs:
if x is s:
s.close()
位運算:
整數之間按照二進位制位進行運算
& 按位與 | 按位或 ^ 按位異或
<< 左移 >>右移
11 1011
14 1110
& 1010 -----> 10 一0則0
| 1111 ----->15 一1則1
^ 0101 ----->5 相同為0不同為1
11<<2 101100 ----->44 乘2
14>>2 11------>3 地板除2
poll方法
1.建立poll物件
p=select.poll()
2.添加註冊事件(後面是監控的事件)發生哪個才返回
p.register(s,POLLIN | POLLERR) 關注s的POLLIN事件
POLLIN POLLOUT POLLERR POLLHUP POLLNVAL
rlist wlist xlist 斷開 無效資料
p.unregister(s)從關注事件中移除(可以是s可以是fileno)
3.阻塞等待IO事件的發生
events=p.poll()
功能:阻塞等待IO發生
返回值:events 是一個列表,列表中每一個元素都是一個元組,代表一個發生的IO事件
[(fileno, event), (),()…]
就緒IO的檔案描述符 具體的 就緒事件
*需要通過檔案描述符(fileno)找到對應的IO物件
需要保證字典{s.fileno()?} 和關注的IO是一致的
4.處理具體的IO
程式碼:
from socket import *
from select import *
#建立套接字作為我們關注的IO
s=socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind((‘0.0.0.0’,8888))
s.listen(5)
#建立一個poll物件
p=poll()
#建一個fileno—>IO物件的字典
fdmap = {s.fileno()?}
#註冊關注的IO
p.register(s,POLLIN | POLLERR)
while True:
#進行IO監控
events = p.poll()
for fd,event in events:
if fd==s.fileno():
c,addr=fdmap[fd].accept()
print(‘Connect from’,addr)
#新增新的關注事件
p.register(c,POLLIN | POLLHUP)
fdmap[c.fileno()]=c
elif event & POLLIN:
data=fdmap[fd].recv(1024)
if not data:
#客戶端退出,從關注中移除
p.unregister(fd)
fdmap[fd].close()
del fdmap[fd]
else:
print(data.decode())
fdmap[fd].send(b’Receive’)
epoll方法:
使用方法:基本與poll方法相同
*將生成物件的poll() 改為 epoll()
*將所有poll物件的事件改為epoll物件事件
只是使用方法相似度高,但是內部作用不同
poll與epoll區別:
1.epoll的效率要比poll和select高的
2.epoll的事件觸發方式更多(EPOLLET邊緣觸發這次訪問不再,等下次一併帶過來;水平觸發是不停的訪問)
程式碼:
from socket import *
from select import *
#建立套接字作為我們關注的IO
s=socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind((‘0.0.0.0’,8888))
s.listen(5)
#建立一個epoll物件
p=epoll()
#建一個fileno—>IO物件的字典
fdmap = {s.fileno()?}
#註冊關注的IO
p.register(s,EPOLLIN | EPOLLERR)
while True:
#進行IO監控
events = p.poll()
for fd,event in events:
if fd==s.fileno():
c,addr=fdmap[fd].accept()
print(‘Connect from’,addr)
#新增新的關注事件
p.register(c,EPOLLIN | EPOLLHUP)
fdmap[c.fileno()]=c
elif event & EPOLLIN:
data=fdmap[fd].recv(1024)
if not data:
#客戶端退出,從關注中移除
p.unregister(fd)
fdmap[fd].close()
del fdmap[fd]
else:
print(data.decode())
fdmap[fd].send(b’Receive’)