IO并发模型
IO 分类
IO分类:阻塞IO ,非阻塞IO,IO多路复用,异步IO等
阻塞IO
1.定义:在执行IO操作时如果执行条件不满足则阻塞。阻塞IO是IO的默认形态。
2.效率:阻塞IO是效率很低的一种IO。但是由于逻辑简单所以是默认IO行为。
3.阻塞情况:
-
因为某种执行条件没有满足造成的函数阻塞
e.g. accept input recv -
处理IO的时间较长产生的阻塞状态
e.g. 网络传输,大文件读写
非阻塞IO
1.定义 :通过修改IO属性行为,使原本阻塞的IO变为非阻塞的状态。
设置套接字为非阻塞IO
sockfd.setblocking(bool)
- 功能:设置套接字为非阻塞IO
- 参数:默认为True,表示套接字IO阻塞;设置为False则套接字IO变为非阻塞
超时检测 :设置一个最长阻塞时间,超过该时间后则不再阻塞等待。
sockfd.settimeout(sec)
- 功能:设置套接字的超时时间
- 参数:设置的时间
1 from socket import * 2 from time import sleep,ctime 3 4 # 日志文件 5 f = open('log.txt','a+') 6 7 # 创建套接字 8 sockfd = socket() 9 sockfd.bind(('127.0.0.1',9999)) 10 sockfd.listen(3) 11 12 # 设置套接字为非阻塞 13 sockfd.setblocking(False) 14 15 # 设置超时检测时间 16 sockfd.settimeout(3) 17 18 while True: 19 print("Waiting for connect...") 20 try: 21 connfd,addr = sockfd.accept() 22 except (BlockingIOError,timeout) as e: 23 # 如果没有客户端连接,每隔3秒写一个日志 24 f.write("%s : %s\n"%(ctime(),e)) 25 f.flush() 26 sleep(3) 27 else: 28 print("Connect from",addr) 29 data = connfd.recv(1024).decode() 30 print(data) 31 32 # Waiting for connect... 33 # Waiting for connect... 34 # Waiting for connect... 35 #log.txt文件逐渐写入 36 # Thu Jun 27 00:29:29 2019 : timed out 37 # Thu Jun 27 00:29:35 2019 : timed out 38 # Thu Jun 27 00:29:41 2019 : timed out
IO多路复用
1.定义:同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率。
2.具体方案:
- select方法 : windows linux unix
- poll方法: linux unix
- epoll方法: linux
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多路复用消耗资源较少,效率较高
1 """ 2 重点代码 3 4 思路分析: 5 1.将关注的IO放入对应的监控类别列表 6 2.通过select函数进行监控 7 3.遍历select返回值列表,确定就绪IO事件 8 4.处理发生的IO事件 9 """ 10 11 from socket import * 12 from select import select 13 14 # 创建一个监听套接字作为关注的IO 15 s = socket() 16 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 17 s.bind(('0.0.0.0',8888)) 18 s.listen(3) 19 20 # 设置关注列表 21 rlist = [s] 22 wlist = [] 23 xlist = [s] 24 25 # 循环监控IO 26 while True: 27 rs,ws,xs = select(rlist,wlist,xlist) 28 # 遍历三个返回列表,处理IO 29 for r in rs: 30 # 根据遍历到IO的不同使用if分情况处理 31 if r is s: 32 c,addr = r.accept() 33 print("Connect from",addr) 34 rlist.append(c) # 增加新的IO事件 35 # else为客户端套接字就绪情况 36 else: 37 data = r.recv(1024) 38 # 客户端退出 39 if not data: 40 rlist.remove(r) # 从关注列表移除 41 r.close() 42 continue # 继续处理其他就绪IO 43 print("Receive:",data.decode()) 44 # r.send(b'OK') 45 # 我们希望主动处理这个IO对象 46 wlist.append(r) 47 48 for w in ws: 49 w.send(b'OK') 50 wlist.remove(w) # 使用后移除 51 52 for x in xs: 53 pass
位运算
定义 : 将整数转换为二进制,按二进制位进行运算
运算符号:
- & 按位与
- | 按位或
- ^ 按位异或
- << 左移
- >> 右移
- 14 --> 01110
- 19 --> 10011
- 14 & 19 = 00010 = 2 一0则0
- 14 | 19 = 11111 = 31 一1则1
- 14 ^ 19 = 11101 = 29 相同为0不同为1
- 14 << 2 = 111000 = 56 向左移动低位补0
- 14 >> 2 = 11 = 3 向右移动去掉低位
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 步骤:
- 创建套接字
- 将套接字register
- 创建查找字典,并维护
- 循环监控IO发生
- 处理发生的IO
1 """ 2 尽量掌握 3 4 思路分析: 5 1. 创建套接字作为监控IO 6 2. 将套接字register 7 3. 创建查找字典,并维护(要时刻与注册IO保持一致) 8 4. 循环监控IO发生 9 5. 处理发生的IO 10 """ 11 12 from socket import * 13 from select import * 14 15 # 创建套接字 16 s = socket() 17 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 18 s.bind(('0.0.0.0',8888)) 19 s.listen(3) 20 21 # 创建poll对象关注s 22 p = poll() 23 24 # 建立查找字典,用于通过fileno查找IO对象 25 fdmap = {s.fileno():s} 26 27 # 关注s 28 p.register(s,POLLIN|POLLERR) 29 30 # 循环监控 31 while True: 32 events = p.poll() 33 # 循环遍历发生的事件 fd-->fileno 34 for fd,event in events: 35 # 区分事件进行处理 36 if fd == s.fileno(): 37 c,addr = fdmap[fd].accept() 38 print("Connect from",addr) 39 # 添加新的关注IO 40 p.register(c,POLLIN|POLLERR) 41 fdmap[c.fileno()] = c # 维护字典 42 # 按位与判定是POLLIN就绪 43 elif event & POLLIN: 44 data = fdmap[fd].recv(1024) 45 if not data: 46 p.unregister(fd) # 取消关注 47 fdmap[fd].close() 48 del fdmap[fd] # 从字典中删除 49 continue 50 print("Receive:",data.decode()) 51 fdmap[fd].send(b'OK')
epoll方法
1.使用方法 : 基本与poll相同
- 生成对象改为 epoll()
- 将所有事件类型改为EPOLL类型
2.epoll特点:
- epoll 效率比select poll要高
- epoll 监控IO数量比select要多
- epoll 的触发方式比poll要多 (EPOLLET边缘触发)
1 """ 2 尽量掌握 3 """ 4 5 from socket import * 6 from select import * 7 8 # 创建套接字 9 s = socket() 10 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 11 s.bind(('0.0.0.0',8888)) 12 s.listen(3) 13 14 # 创建epoll对象关注s 15 ep = epoll() 16 17 # 建立查找字典,用于通过fileno查找IO对象 18 fdmap = {s.fileno():s} 19 20 # 关注s 21 ep.register(s,EPOLLIN|EPOLLERR) 22 23 # 循环监控 24 while True: 25 events = ep.poll() 26 # 循环遍历发生的事件 fd-->fileno 27 for fd,event in events: 28 print("亲,你有IO需要处理哦") 29 # 区分事件进行处理 30 if fd == s.fileno(): 31 c,addr = fdmap[fd].accept() 32 print("Connect from",addr) 33 # 添加新的关注IO 34 # 将触发方式变为边缘触发 35 ep.register(c,EPOLLIN|EPOLLERR|EPOLLET) 36 fdmap[c.fileno()] = c # 维护字典 37 # 按位与判定是EPOLLIN就绪 38 # elif event & EPOLLIN: 39 # data = fdmap[fd].recv(1024) 40 # if not data: 41 # ep.unregister(fd) # 取消关注 42 # fdmap[fd].close() 43 # del fdmap[fd] # 从字典中删除 44 # continue 45 # print("Receive:",data.decode()) 46 # fdmap[fd].send(b'OK')