Socket+Select 实现单线程并发请求
涉及知识点:
- socket 编程;
- http 协议中的请求和响应的基本格式;
- select
目的:
- 理解单线程并发请求的基本实现方式;
代码
import socket
from urllib.parse import urlparse
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
class Connection:
def __init__(self, url, loop):
self.loop = loop
self.selector = loop.selector
self.loop.task_count += 1
url = urlparse(url)
host_and_port = url.netloc.split(":")
self.host = host_and_port[0]
if len(host_and_port) == 2:
self.port = int(host_and_port[1])
else:
self.port = 80
self.path = url.path
if self.path == "":
self.path = "/"
self.data = b""
# 建立socket连接
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接建立成功后调用self.connected
self.selector.register(self.client.fileno(), EVENT_WRITE, self.connected)
self.client.connect((self.host, self.port))
self.client.setblocking(False)
def connected(self, key):
self.selector.unregister(key.fd)
self.client.send(
"GET {} HTTP/1.1
Host:{}
Connection:close
".format(self.path, self.host).encode("utf8"))
# 有数据返回时调用self.readable
self.selector.register(self.client.fileno(), EVENT_READ, self.readable)
def readable(self, key):
d = self.client.recv(1024)
if d:
self.data += d
else:
self.selector.unregister(key.fd)
resp_body = self.data.decode("utf8").split("
")[1]
self.loop.results.append(resp_body)
self.client.close()
class Loop:
def __init__(self):
# 任务数量
self.task_count = 0
self.stop = False
self.selector = DefaultSelector()
self.results = []
def run(self):
while self.task_count > len(self.results):
ready = self.selector.select()
for key, mask in ready:
key.data(key)
return self.results
if __name__ == "__main__":
loop = Loop()
base_url = "http://127.0.0.1:5001/items/{}"
for i in range(1, 5):
con = Connection(base_url.format(i), loop)
res = loop.run()
print(res)
# 假设一个请求需要一秒, 如果使用传统同步请求的方式,那么5个请求就需要5秒中;
# 使用代码中这种方式一共只需要1秒时间, 因为5个请求是同时发出去的.