Socket+Select 实现单线程并发请求

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\r\nHost:{}\r\nConnection:close\r\n\r\n".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("\r\n\r\n")[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个请求是同时发出去的.
posted @ 2020-08-02 12:00  Aloe_n  阅读(305)  评论(0编辑  收藏  举报