• 聊聊异步非阻塞IO


    io异步模块

    知识必备:

    socket实现web请求

    如果说socket是所有web操作的本质,爬虫在web操作中扮演的角色,应该就是一个“客户端”。

    obj=socket()
    obj.connect((192.168.1.1,80))
    # socket 连接是一个耗时阻塞的操作
    obj.send('get /url http1.1 
     host:...
     content-type 
    
    ')
    # 通过
    将请求参数分割开按照一定格式发送给服务端
    

    下面是一段用socket向百度发送请求的实例:

    import socket
    client=socket.socket()
    client.connect(('61.135.169.125',80)) # 阻塞
    data=b'GET / HTTP/1.0
    host: www.baidu.com
    
    '
    # 请求,访问的url(这里无,所以用/)然后是协议:http1.1
    # 最后一定要记得用
    分割开,因为服务器靠这个来split信息
    client.sendall(data)
    response=client.recv(8096) # 阻塞
    print(response)
    client.close()
    

    可以看到我在上面两个地方标注了“阻塞”。

    如果想要让这个socket不发生“阻塞”,其实只需要

    client.setblocking(False)
    

    但是这种操作带来的问题是:

    BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。
    

    暂时不考虑这个错误,我们其实已经将这个socket做成了一个“非阻塞”的socket程序了,想要让他正常的运行,可以通过time模块来模拟一些其他计算耗时或者叫做“异步任务”。
    代码如下:

    client=socket.socket()
    client.setblocking(False)
    try:
        client.connect(('61.135.169.125',80)) # 阻塞
    except BlockingIOError as e:
        print(e)
    import time
    time.sleep(3)   # 假设这里有一个耗时3秒的操作,操作完后client.connect已经完成
    data=b'GET / HTTP/1.0
    host: www.baidu.com
    
    '
    client.sendall(data)
    time.sleep(3)  # 假设这里又有一个操作,结束后client已经获取了服务器返回值
    response=client.recv(8096) # 阻塞
    print(response)
    client.close()
    

    观察上面代码的结果,可以发现连接一开始被判断不成功的时候的确返回了错误信息,但是最终我们还是获取到了网页的信息,说明最后连接完成后的整个流程是被正确地走完了的。其中的sleep可以替换成任何其他操作,可以去读取获取文件,进行计算等等……

    通过合理安排各个任务,既节约了任务的时间,并且完成了多任务的效果。这就是异步IO

    但是我们怎么样判断“第一个任务(连接)阻塞的期间,我们可以完成多少别的任务”,“假如我们的支线任务做完后,主线任务仍然没有结束,我们该干什么”。
    利用while循环判断主线任务是否返回,如果返回了值,就break进行下一步,如果没有,就继续进行支线任务

    总结:
    非阻塞---报错---利用try让程序继续运行
    定义一些操作(把所有的请求,在第一个请求发送的等待期间,全部发送过去。)

    io多路复用

    用于检测【多个】IO对象(socket对象)是否有变化。

    r,w,e=selcet.select([socket,socket.....],[],[],0.5)
    # 第一个传入参数为socket对象列表
    #  
    
    • w:返回一个列表,表示“连接成功”
    • r:返回一个列表,如果socket中返回内容,就表示“需要接受数据”,这个就会被进入进r返回的列表中

    完成代码:

    import socket
    import select
    
    class ReqIO(object):
        def __init__(self,sock,info):
            self.sock=sock
            self.info=info
        def fileno(self):
            return self.sock.fileno()
    
    class IOtest(object):
        def __init__(self):
            self.socklist=[]
            self.conns=[]
    
        def add_request(self,req_info):
            sock=socket.socket()
            sock.setblocking(False)
            try:
                sock.connect((req_info['host'],req_info['port']))
            except Exception as e:
                pass
      obj=ReqIO(sock,req_info)
            self.socklist.append(obj)
            self.conns.append(obj)
    
        def run(self):
            while True:
                r,w,e=select.select(self.socklist,self.conns,[],0.05)
                """
      补充:select中添加的可以是任何对象,但是这个对象一定要有fileno方法
     w 是否连接成功
     检查循环到的i是哪个字典
     """
      for i in w:
                    data = 'GET %s HTTP/1.0
    host: %s
    
    '%(i.info['path'],i.info['host'])
                    i.sock.send(data.encode('utf-8'))
                    self.conns.remove(i)
                """
      这时候w中的元素是ReqIO对象,但是他仍然能够连接成功,
     并且他会利用封装过的info来获取当前i的info
     """
      for j in r:
                    """
      数据返回接收数据
     """  response = j.sock.recv(8096)
                    print(j.info['host'],':
    ',response)
                    self.socklist.remove(j)
    
                if not self.socklist:
                    # 当所有请求都已经返回
      break
    
    url_list=[
        {'host':'www.baidu.com','IP':'61.135.169.125','port':80,'path':'/'},
      {'host':'dig.chouti.com','IP':'111.206.193.95','port':80,'path':'/'},
      {'host':'www.bing.com','IP':'118.178.213.186','port':80,'path':'/'}
    ]
    
    test=IOtest()
    
    for item in url_list:
        test.add_request(item)
    
    test.run()
    
  • 相关阅读:
    css选取第几个元素的方法
    如何瓦解巨石单页应用
    C#获取本地或远程磁盘使用信息
    文档管理使用语雀
    位运算反(~)与(&)异或(^)或(|)右移(>>)左移(<<)
    面试题大全
    关于普通盒子模型contentbox和怪异盒子模型borderbox
    vue3.0和vue2.0的区别是什么?
    .NET Core(.NET6)中gRPC注册到Consul
    alifd的深度教程
  • 原文地址:https://www.cnblogs.com/scott-lv/p/7753882.html
Copyright © 2020-2023  润新知