1. 程式人生 > 實用技巧 >Socket+Select 實現單執行緒併發請求

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個請求是同時發出去的.