十六、 IO多路复用,异步非阻塞

总结:

与实现twisted或tornado的原理类似,通过理解这个代码,能实现其他异步框架的理解

参考:

IO模型:https://www.cnblogs.com/nuochengze/p/13372747.html

socket在爬虫中的表层应用:https://www.cnblogs.com/nuochengze/p/13345532.html

代码

import socket
import select


class Request(object):
    def __init__(self, sock, info):
        """
        sock::socket
        """
        self.sock = sock
        self.info = info

    def fileno(self):
        # Request对象原本没有fileno方法,但是可以调用传递进来的sock对象的fileno方法
        return self.sock.fileno()


class AsyncioNoBlocking(object):
    def __init__(self):
        self.socket_list = list()
        self.connect_list = list()

    def add_request(self, req_info):
        """
        创建请求
        req_info::dict ::{"host": 'www.baidu.com', 'port': 80, path: '/'},
        """
        sock = socket.socket()
        sock.setblocking(False)
        try:
            sock.connect((req_info['host'], req_info['port']))
        except BlockingIOError as e:
            pass

        sock = Request(sock, req_info)  # 将sock对象进行封装了
        self.socket_list.append(sock)
        self.connect_list.append(sock)

    def run(self):
        """
        开始事件循环,检测是否链接成功及是否数据返回
        """
        while True:
            # select.select()中的对象,可以是除socket对象,其他的任何对象,但是这个其他对象需要有fileno方法, 即`对象.fileno()`
            r_list, w_list, e_list = select.select(self.socket_list, self.connect_list, [], 0.05)
            # 此时select.select([obj])中的obj是已经封装好的Request(自定义)对象
            # w_list,当其内有值时,表示链接成功
            for w in w_list:
                # 检查w是哪一个Request对象
                # 此时Request对象中包含url的信息
                data = "GET %s http/1.1
host:%s

" % (w.info['path'], w.info['host'])
                w.sock.send(data.encode('utf8'))  # 发送数据
                self.connect_list.remove(w)  # 避免发送两次请求,需要将已发送的请求移除
            # r_list,当其内有值时,表示有返回的数据
            for r in r_list:
                response_str = str()
                while True:
                    response = r.sock.recv(1024)
                    if len(response) == 0:
                        break
                    response_str += response
                # print(r.info['host'], response_str)  # 打印内容,后续可通过return返回数据
                # 也可通过回调函数,实现对数据的自定义处理
                for func in r.info['callback']:
                    """
                    对返回的数据,如何处理,都将通过自己定义的回调函数,来进行处理
                    """
                    func(response)
                self.socket_list.remove(r)  # 移除socket对象,避免重复接受数据
            if not r_list:
                """
                结束循环,需要进行判断
                w_list表示成功链接的请求,可能链接成功,但是没有返回数据,所以不能作为判断结束条件
                r_list表示链接返回的数据,当为空的时候,表示该Request对象不再有成功链接,表示请求结束了,可退出循环
                """
                break


def done1(response):
    # 对response进行操作
    pass


def done2(response):
    # 对response进行操作
    pass


url_list = [
    {"host": 'www.baidu.com', 'port': 80, "path": '/', "callback": [done1]},
    {"host": 'www.bing.com', 'port': 80, "path": '/', "callback": [done2]},
]

if __name__ == "__main__":
    obj = AsyncioNoBlocking()
    for item in url_list:
        # 遍历列表,发送请求
        obj.add_request(item)
    # 开始事件循环
    obj.run()
View Code
原文地址:https://www.cnblogs.com/nuochengze/p/13375787.html