服务端异步IO配合协程浅析
代码如下
#coding:utf-8
import socket
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
selector = DefaultSelector()
stopped = False
response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n'
response += b'hello world'
class Future:
def __init__(self):
self.result = None
self._callback = []
def add_done_callback(self, fn):
self._callback.append(fn)
def set_result(self, result):
self.result = result
for fn in self._callback:
fn(self)
def __iter__(self):
yield self
return self.result
class Task:
def __init__(self, coro):
self.coro = coro
f = Future()
f.set_result(None)
self.step(f)
def step(self, future):
try:
next_future = self.coro.send(future.result)
print("next_future: ", next_future)
except StopIteration as e:
print("step stop :", e.value)
return
next_future.add_done_callback(self.step)
class ConHandler(object):
def __init__(self, con, addr):
self.con = con # 新连接实例
self.addr = addr # 新连接地址
def _read_ready(self):
f = Future()
def sock_read(): # 由于没有解析相应的流协议,所以这里直接接受所有的数据,
# 如果解析具体协议也可以改写成协程
all_data = b"" # 接受数据
while True: # 一直接受数据直到数据接受完成
try:
data = self.con.recv(10)
if data:
print("recv data : ", data)
all_data += data
else:
print("break while")
break
except BlockingIOError:
print("BlockingIOError")
break
print("all_data", all_data)
# selector.unregister(self.con.fileno())
f.set_result(None) # 当数据接受完成后,调用f实例的回调方法,进入将主流程往下执行一步
selector.register(self.con.fileno(), EVENT_READ, sock_read) # 注册连接的读事件并注册回调函数
yield f
selector.unregister(self.con.fileno()) # 取消连接的注册事件
def fetch(self):
# 读事件到来一次读完
fu = yield from self._read_ready() # 调用读数据
print("recv_data: ", fu)
# handler 处理请求
self.response = response*10000
result = yield from self.sock_send_all() # 将处理的数据发送出去
print("send after : ", result)
def sock_send_all(self):
send_length = yield from self.sock_send() # 发送相应数据,返回发送的数据长度
self.response = self.response[send_length:] # 减去已经发送的数据长度
while send_length: # 如果发送长度不为0
send_length = yield from self.sock_send() # 继续发送数据
self.response = self.response[send_length:] # 减去已经发送的数据长度
return
def sock_send(self):
f = Future()
def _sock_send():
con_length = self.con.send(self.response)
f.set_result(con_length)
selector.register(self.con.fileno(), EVENT_WRITE, _sock_send)
send_length = yield f
selector.unregister(self.con.fileno())
return send_length
def create_server():
sock = socket.socket() # 创建连接
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 设置端口重用
sock.bind(('0.0.0.0', 9003)) # 设置监听连接端口
sock.listen(10) # 设置监听缓存区
sock.setblocking(0) # 设置连接非阻塞
def sock_accept(): # 如果sock的新请求连接处理回调函数
try:
conn, addr = sock.accept() # 接受新请求
conn.setblocking(False) # 设置接受新连接非阻塞
print(conn)
except Exception:
pass
Task(ConHandler(conn, addr).fetch()) # 用任务类包裹处理流程,使之流程协程处理
selector.register(sock.fileno(), EVENT_READ, sock_accept) # 注册sock的连接读事件并设置读事件的回调函数
def loop():
while not stopped:
events = selector.select() # 获取触发的事件
for event_key, event_mask in events:
callback = event_key.data # 获取触发事件的回调函数
callback() # 执行回调函数
if __name__ == '__main__':
create_server()
loop()
基于上一篇的异步协程爬虫,现在服务端的大概原理是:
1.注册服务端连接的读事件和读回调函数,
2.读回调函数,将接受的新请求传入接受处理函数,并包装成Task类执行,
3.注册新连接的读事件,接受完成数据后,调用Task往下执行,
4.处理完成后,将处理的数据写出时,注册新连接的写事件,如果数据未写完继续执行,循环执行,直到数据写出完成
5.取消连接的事件监听
6.继续等待新连接的介入
至此上述代码的执行流程已完成。