聊聊異步非阻塞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()
相關文章
相關標籤/搜索