IO分类:
阻塞IO ,非阻塞IO,IO多路复用,异步IO等。
阻塞IO:
(执行条件不满足)是IO的默认形态。是一种效率十分低的一种IO。
发生阻塞的情况:
- 因为某种执行条件没有满足造成的函数阻塞;
- 处理IO的时间较长产生的阻塞状态。
非阻塞IO:
通过修改IO属性行为,使原本阻塞的IO变为非阻塞的状态。
设置套接字为非阻塞IO.sockfd.setblocking(bool)
功能:设置套接字为非阻塞IO。
参数:True,表示套接字IO阻塞;设置False则套接字IO变为非阻塞。
超时检测:设置一个最长的阻塞时间,超过时间后则不在阻塞等待。
sockfd.settimeout(sec)
功能:设置套接字的超时间。
参数:设置时间。
代码实现演示:
"""
block_io.py 非阻塞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:
c,addr = s.accept()
print("Connect from",addr)
except (BlockingIOError,timeout) as e:
sleep(2)
f.write(ctime()+':'+str(e)+'\n')
else:
data = c.recv(1024)
print(data)
IO多路复用:
定义:同时监控多个IO事件,当那个IO事件准备就绪就执行那个IO事件。以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成其他IO无法执行,提高IO执行效率。
实现多路复用的方法有:
select:windows ,linux,unix
poll:Linux,unix
epoll:linux
select方法代码演示:
"""
select 演示
"""
from select import select
from socket import *
s = socket()
s.bind(('127.0.0.1',8888))
s.listen(3)
f = open('12.txt')
print(" 监控io ")
rs,ws,xs = select([s],[s],[s])
print("rlist:",rs)
print("wlist:",ws)
print("xlist:",xs)
"""
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)
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)
# r.send(b'OK')
wlist.append(r)
for w in ws:
w.send(b'OK')
wlist.remove(w) # 从写监控中移除
poll方法实现代码演示:
"""
poll 方法实现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)
# fdmap[fd].send(b'OK')
elif event & POLLOUT:
fdmap[fd].send(b'OK')
p.register(fd, POLLIN)
p.register(fd,event):
- 功能: 注册关注的IO事件
- 参数:fd 要关注的IO
【1】. event 要关注的IO事件类型
【2】. 常用类型:
POLLIN 读IO事件(rlist)
POLLOUT 写IO事件 (wlist)
POLLERR 异常IO (xlist)
POLLHUP 断开连接
e.g. p.register(sockfd,POLLIN|POLLERR)
p.unregister(fd):
- 功能:取消对IO的关注
- 参数:IO对象或者IO对象的fileno
events = p.poll():
- 功能: 阻塞等待监控的IO事件发生
- 返回值: 返回发生的IO
【1】events格式 [(fileno,event),()…]
【2】每个元组为一个就绪IO,元组第一项是该IO的fileno,第二项为该IO就绪的事件类型。
epoll方式实现代码:
"""
epoll 方法实现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)
epoll特点:
- epoll 效率比select poll要高
- epoll 监控IO数量比select要多
- epoll 的触发方式比poll要多 (EPOLLET边缘触发)