IO多路复用
1. 定义
同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。以此形成可以同时处理多个IO
的行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率。
2. 具体方案
select方法 : windows linux unix
poll方法: linux unix
epoll方法: linux
select 方法
代码实现: day10/select_server.py
rs, ws, xs=select(rlist, wlist, xlist[, timeout])
功能: 监控IO事件,阻塞等待IO发生
参数:rlist 列表 读IO列表,添加等待发生的或者可读的IO事件
wlist 列表 写IO列表,存放要可以主动处理的或者可写的IO事件
xlist 列表 异常IO列表,存放出现异常要处理的IO事件
timeout 超时时间
返回值: rs 列表 rlist中准备就绪的IO
ws 列表 wlist中准备就绪的IO
xs 列表 xlist中准备就绪的IO
select 实现tcp服务
【1】将关注的IO放入对应的监控类别列表
【2】通过select函数进行监控
【3】遍历select返回值列表,确定就绪IO事件
【4】处理发生的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)
# 设置关注的IO
rlist = [s] # s的读IO行为
wlist = []
xlist = []
while True:
# 循环监控s
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) # 不再关注这个IO
r.close()
continue
print(data)
# r.send(b'OK')
wlist.append(r) #加入写IO
for w in ws:
w.send(b'OK')
wlist.remove(w)
注意
wlist中如果存在IO事件,则select立即返回给ws
处理IO过程中不要出现死循环占有服务端的情况
IO多路复用消耗资源较少,效率较高
@@扩展: 位运算
定义 : 将整数转换为二进制,按二进制位进行运算
运算符号:
& 按位与
| 按位或
^ 按位异或
<< 左移
>>右移
e.g. 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方法
代码实现: day10/poll_server.py
p = select.poll()
功能 : 创建poll对象
返回值: poll对象
p.register(fd,event)
功能: 注册关注的IO事件
参数:fd 要关注的IO
event 要关注的IO事件类型
常用类型: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
events格式 [(fileno,event),()…]
每个元组为一个就绪IO,元组第一项是该IO的fileno,第二项为该IO就绪的事件类型
poll_server 步骤
【1】 创建套接字
【2】 将套接字register
【3】 创建查找字典,并维护
【4】 循环监控IO发生
【5】 处理发生的IO
"""
poll_server.py tcp服务
重点代码
思路: poll() 的返回值不是IO对象
建立字典 {fileno:io_obj}
"""
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()
# 建立查找字典
fdmap = {s.fileno():s}
# 关注s套接字
p.register(s,POLLIN)
# 循环监控IO发生
while True:
# 提交监控
events = p.poll()
print(events)
for fd,event in events:
if fd == s.fileno():
c,addr = s.accept()
print("Connect from",addr)
p.register(c,POLLIN|POLLERR) # 添加新的关注IO
fdmap[c.fileno()] = c # 注意维护字典与register保持一致
elif event & POLLIN:
# 通过文件描述符取得对象
data = fdmap[fd].recv(1024).decode()
if not data:
p.unregister(fd) # 不再关注
fdmap[fd].close()
del fdmap[fd] # 从字典删除
continue
print(data)
fdmap[fd].send(b'OK')
epoll方法
代码实现: day10/epoll_server.py
- 使用方法 : 基本与poll相同
生成对象改为 epoll()
将所有事件类型改为EPOLL类型 - epoll特点
epoll 效率比select poll要高
epoll 监控IO数量比select要多
epoll 的触发方式比poll要多 (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(3)
# 创建epoll对象
p = epoll()
# 建立查找字典
fdmap = {s.fileno():s}
# 关注s套接字
p.register(s,EPOLLIN)
# 循环监控IO发生
while True:
# 提交监控
events = p.poll()
print("你有新的IO需要处理哦")
for fd,event in events:
if fd == s.fileno():
c,addr = s.accept()
print("Connect from",addr)
# 添加新的关注IO
p.register(c,EPOLLIN|EPOLLERR|EPOLLET) # 边缘触发
fdmap[c.fileno()] = c # 注意维护字典与register保持一致
# elif event & EPOLLIN:
# # 通过文件描述符取得对象
# data = fdmap[fd].recv(1024).decode()
# if not data:
# p.unregister(fd) # 不再关注
# fdmap[fd].close()
# del fdmap[fd] # 从字典删除
# continue
# print(data)
# fdmap[fd].send(b'OK')
HTTPServer v2.0
day10/http_server.py
- 主要功能 :
【1】 接收客户端(浏览器)请求
【2】 解析客户端发送的请求
【3】 根据请求组织数据内容
【4】 将数据内容形成http响应格式返回给浏览器 - 升级点 :
【1】 采用IO并发,可以满足多个客户端同时发起请求情况
【2】 通过类接口形式进行功能封装
【3】 做基本的请求解析,根据具体请求返回具体内容,同时处理客户端的非网页请求行为
"""
httpserver 2.0
"""
from socket import *
from select import select
class HTTPServer:
def __init__(self,host='0.0.0.0',port=80,dir=None):
self.host = host
self.port = port
self.address = (host,port)
self.dir = dir
self.rlist = []
self.wlist = []
self.xlist = []
# 直接创建套接字
self.create_socket()
# 创建套接字
def create_socket(self):
self.sockfd = socket()
self.sockfd.setsockopt(SOL_SOCKET,
SO_REUSEADDR,
1)
self.sockfd.bind(self.address)
# 启动服务
def serve_forever(self):
self.sockfd.listen(3)
print("Listen the port %d"%self.port)
# IO多路服用方法监控IO
self.rlist.append(self.sockfd)
while True:
rs,ws,xs=select(self.rlist,
self.wlist,
self.xlist)
for r in rs:
if r is self.sockfd:
# 浏览器链接
c,addr = r.accept()
self.rlist.append(c)
else:
# 处理具体请求
self.handle(r)
# 处理客户端请求
def handle(self,connfd):
request = connfd.recv(4096).decode()
# 客户端断开
if not request:
self.rlist.remove(connfd)
connfd.close()
return
# 解析请求,提取请求内容
request_line = request.split('\n')[0]
info = request_line.split(' ')[1]
print(connfd.getpeername(),':',info)
# 根据请求内容将其分为两类
if info == '/' or info[-5:] == '.html':
self.get_html(connfd,info)
else:
self.get_data(connfd,info)
connfd.close()
self.rlist.remove(connfd)
def get_data(self,connfd,info):
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type:text/html\r\n"
response += '\r\n'
response += "<h1>Waiting for httpserver 3.0</h1>"
connfd.send(response.encode())
# 处理网页
def get_html(self,connfd,info):
if info == '/':
# 要主页
filename = self.dir+'/index.html'
else:
# 具体的网页
filename = self.dir + info
try:
fd = open(filename)
except Exception:
response = "HTTP/1.1 404 Not Found\r\n"
response += "Content-Type:text/html\r\n"
response += '\r\n'
response += "<h1>Sorry....</h1>"
else:
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type:text/html\r\n"
response += '\r\n'
response += fd.read()
finally:
# 将响应发送给浏览器
connfd.send(response.encode())
if __name__ == '__main__':
# 通过HTTPServer类快速搭建服务
# 通过该服务让浏览器访问到我的网页
# 1. 使用流程
# 2. 需要用户确定的内容
# 用户决定的参数
HOST = '0.0.0.0'
PORT = 8000
DIR = './static'
httpd = HTTPServer(HOST,PORT,DIR) # 生成对象
httpd.serve_forever() # 启动服务