聊聊异步非阻塞IO

io异步模块

知识必备:

socket实现web请求

若是说socket是全部web操做的本质,爬虫在web操做中扮演的角色,应该就是一个“客户端”。python

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

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

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

能够看到我在上面两个地方标注了“阻塞”。服务器

若是想要让这个socket不发生“阻塞”,其实只须要app

client.setblocking(False)

可是这种操做带来的问题是:异步

BlockingIOError: [WinError 10035] 没法当即完成一个非阻止性套接字操做。

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

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\r\nhost: www.baidu.com\r\n\r\n'
client.sendall(data)
time.sleep(3)  # 假设这里又有一个操做,结束后client已经获取了服务器返回值
response=client.recv(8096) # 阻塞
print(response)
client.close()

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

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

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

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

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\r\nhost: %s\r\n\r\n'%(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'],':\r\n',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()
相关文章
相关标签/搜索