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)