IO 分類
IO分類:阻塞IO ,非阻塞IO,IO多路複用,異步IO等。
阻塞IO
1.定義:在執行IO操作時如果執行條件不滿足則阻塞。阻塞IO是IO的默認形態。
2.效率:阻塞IO是效率很低的一種IO。但是由於邏輯簡單所以是默認IO行爲。
3.阻塞情況:
因爲某種執行條件沒有滿足造成的函數阻塞 (accept input recv)
處理IO的時間較長產生的阻塞狀態 (網絡傳輸,大文件讀寫)
非阻塞IO
1. 定義 :通過修改IO屬性行爲,使原本阻塞的IO變爲非阻塞的狀態。
設置套接字爲非阻塞IO
sockfd.setblocking(bool)
功能:設置套接字爲非阻塞IO
參數:默認爲True,表示套接字IO阻塞;設置爲False則套接字IO變爲非阻塞
超時檢測 :設置一個最長阻塞時間,超過該時間後則不再阻塞等待。
sockfd.settimeout(sec)
功能:設置套接字的超時時間
參數:設置的時間
示例:
"""
非阻塞io演示
"""
from socket import *
from time import ctime, sleep
f = open('log.txt', 'a') # 日誌文件
s = socket()
s.bind(('0.0.0.0', 8888))
s.listen(5)
# 設置套接字非阻塞
# s.setblocking(False)
# 設置超時時間
s.settimeout(3)
while True:
try:
# 設置非阻塞時會直接進入except
# 設置阻塞,超時時間3秒,等待3秒若無客戶端連接,進入except
c, addr = s.accept()
print("Connect from", addr)
except (BlockingIOError, timeout) as e:
print(e)
sleep(2)
f.write(ctime() + ':' + str(e) + '\n')
f.flush()
else:
data = c.recv(1024)
print(data)
結果:
IO多路複用
1. 定義
同時監控多個IO事件,當哪個IO事件準備就緒就執行哪個IO事件。以此形成可以同時處理多個IO的行爲,避免一個IO阻塞造成
其他IO均無法執行,提高了IO執行效率。
2. 具體方案
【1】select方法 : windows linux unix
【2】poll方法: linux unix
【3】epoll方法: linux
【1】select 方法
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
select 實現tcp服務
【1】 將關注的IO放入對應的監控類別列表
【2】通過select函數進行監控
【3】遍歷select返回值列表,確定就緒IO事件
【4】處理髮生的IO事件
注意
wlist中如果存在IO事件,則select立即返回給ws
處理IO過程中不要出現死循環佔有服務端的情況
IO多路複用消耗資源較少,效率較高
示例:
"""
select tcp 服務
1.將關注的IO放入對應的監控類別列表
2.通過select函數進行監控
3.遍歷select返回值列表,確定就緒IO事件
4.處理髮生的IO事件
"""
from socket import *
from select import select
# 創建監聽套接字作爲關注IO
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 8888))
s.listen(3)
# 設置關注列表
rlist = [s] # 等待客戶端連接
wlist = []
xlist = []
# 監控IO發生
while True:
rs, ws, xs = select(rlist, wlist, xlist)
# 遍歷等待處理類型的IO(等待客戶端連接)
for r in rs:
if r is s:
# 有客戶端連接
c, addr = r.accept()
print("Connect from", addr)
rlist.append(c) # 客戶端連接對象加入監控
else:
# 如果是客戶端連接發來信息
data = r.recv(1024).decode()
if not data: # 斷開連接
rlist.remove(r) # 取消對它關注
r.close()
continue
print(data)
# 將立即發送回覆消息的IO添加到主動處理IO檢測列表裏會立即觸發執行
wlist.append(r)
for w in ws:
w.send(b'OK')
wlist.remove(w) # 從主動執行監控IO列表中移除
【2】poll方法
p = select.poll()
功能 : 創建poll對象
返回值: poll對象
p.register(fd,event)
功能: 註冊關注的IO事件
參數:fd 要關注的IO
event 要關注的IO事件類型
常用類型:POLLIN 讀IO事件(rlist)
POLLOUT 寫IO事件 (wlist)
POLLERR 異常IO (xlist)
POLLHUP 斷開連接
比如:p.register(sockfd,POLLIN|POLLERR)
p.unregister(fd)
功能:取消對IO的關注
參數:IO對象或者IO對象的fileno
events = p.poll()
功能: 阻塞等待監控的IO事件發生
返回值: 返回發生的IO
events格式 [(fileno,event),()....]
每個元組爲一個就緒IO,元組第一項是該IO的fileno,第二項爲該IO就緒的事件類型
poll_server 步驟
【1】 創建套接字
【2】 將套接字register
【3】 創建查找字典,並維護
【4】 循環監控IO發生
【5】 處理髮生的IO
示例:
"""
方法實現IO多路複用
【1】 創建套接字
【2】 將套接字register
【3】 創建查找字典,並維護
【4】 循環監控IO發生
【5】 處理髮生的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(3)
# 創建poll對象
p = poll()
# 建立查找字典,通過IO對象的fileno找到對象
# 字典內容與關注IO保持一直{fileno:io_obj}
fdmap = {s.fileno(): s}
# 關注s
p.register(s, POLLIN | POLLERR)
# 循環監控IO的發生
while True:
events = p.poll()
print(events)
for fd, event in events:
if fd == s.fileno():
c, addr = fdmap[fd].accept()
print("Connect from", addr)
# 添加新的關注對象,同時維護字典
p.register(c, POLLIN)
fdmap[c.fileno()] = c
elif event & POLLIN:
data = fdmap[fd].recv(1024).decode()
if not data:
# 客戶端退出
p.unregister(fd) # 取消關注
fdmap[fd].close()
del fdmap[fd]
continue
print(data)
p.register(fd, POLLOUT)
elif event & POLLOUT:
fdmap[fd].send(b'OK')
p.register(fd, POLLIN)
【3】epoll方法
1. 使用方法 :
基本與poll相同
生成對象改爲 epoll()
將所有事件類型改爲EPOLL類型
2. epoll特點:
epoll 效率比select poll要高
epoll 監控IO數量比select要多
epoll 的觸發方式比poll要多 (EPOLLET邊緣觸發)
示例:
"""
方法實現IO多路服用
【1】 創建套接字
【2】 將套接字register
【3】 創建查找字典,並維護
【4】 循環監控IO發生
【5】 處理髮生的IO
"""
from socket import *
from select import *
# 創建套接字作爲關注IO
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 8888))
s.listen(3)
# 創建epoll對象
ep = epoll()
# 建立查找字典,通過IO對象的fileno找到對象
# 字典內容與關注IO保持一直{fileno:io_obj}
fdmap = {s.fileno(): s}
# 關注s
ep.register(s, EPOLLIN | EPOLLERR)
# 循環監控IO的發生
while True:
events = ep.poll()
print("你有新的IO需要處理哦:", events)
for fd, event in events:
if fd == s.fileno():
c, addr = fdmap[fd].accept()
print("Connect from", addr)
# 添加新的關注對象,同時維護字典
ep.register(c, EPOLLIN | EPOLLET) # 邊緣觸發
fdmap[c.fileno()] = c
elif event & EPOLLIN:
data = fdmap[fd].recv(1024).decode()
if not data:
# 客戶端退出
ep.unregister(fd) # 取消關注
fdmap[fd].close()
del fdmap[fd]
continue
print(data)
ep.unregister(fd)
ep.register(fd, POLLOUT)
elif event & POLLOUT:
fdmap[fd].send(b'OK')
ep.unregister(fd)
ep.register(fd, POLLIN)