阻塞IO即為之前正常使用的IO 邏輯簡單 非阻塞IO 可以把阻塞IO 設定為非阻塞IO,例如sockfd.setblocking(false)。如果設定成了非阻塞,無客戶端連線時就會報BlockingIOError錯誤,透過try來捕獲。透過迴圈來接受客戶端連線
還可以設定超時檢測,settimeout---sockfd.settimeout(5)超時報錯
while True: print("Waiting from connect...") try: connfd,addr = sockfd.accept() except (BlockingIOError,timeout) as e: sleep(2) f.write("%s : %s\n"%(ctime(),e)) f.flush() else: print("Connect from",addr) data = connfd.recv(1024).decode() print(data)
IO多路複用:select poll epoll
elect適用於window,Linux ,unix,poll:inux unix epoll:unix
select最多監控1024個IO
poll比select監控的IO數量多
epoll比select poll的效率高,監控的IO多,觸發方式多
select:把要監測的IO放入rlist,wlist,xlist中,rlist適用於被動接受,wlist適用於主動寫入,xlist出現錯誤出發(一般不用)。select()函式返回值也是3個列表,迴圈3個列表處理IO就緒事件
from select import select from socket import * s=socket() s.bind(('0.0.0.0',8000)) s.listen(5) rlist=[s] wlist=[] xlist=[] while True: rs,ws,xs=select(rlist,wlist,xlist) for r in rs: if r==s: conf, addr = r.accept() print('監聽到',addr,'的連結') rlist.append(conf) else: data=r.recv(1024) print(data) r.send(b'ok') if not data: r.close() rlist.remove(r) for w in ws: pass
poll:from select import poll
用到的函式有:p=poll()(建立poll物件),p.register(IO物件,POLLIN|POLLERR)(註冊IO事件),p.unregister(IO物件或檔案描述符) (取消註冊)“event & POLLIN” 按位與檢查是否檢測POLLIN事件(讀IO事件)。events=p.poll()(阻塞等待監控IO事件發生)events為列表裡面套元組[(fileno,event),()] 要自己做fileno和IO物件的對映字典,動態維護字典
from select import * from socket import * s=socket() s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('0.0.0.0',8887)) s.listen(3) p=poll() fdmap={s.fileno():s} p.register(s,POLLIN) while True: events=p.poll() for fileno,event in events: if fileno==s.fileno(): conf,addr=s.accept() p.register(conf,POLLIN|POLLERR) fdmap[conf.fileno()]=conf elif event & POLLIN: data=fdmap[fileno].recv(1024).decode() print(data) if not data: #客戶端退出,unregister,字典刪除,套接字關閉 p.unregister(fileno) fdmap[fileno].close() del fdmap[fileno] continue fdmap[fileno].send(b'ok')
epoll使用方法和poll一樣,要加E
協程
asyncio模組
普通函式def之前加async關鍵字 async def get_request()...
函式呼叫生成協程物件c=get_request()
建立任務物件 task=asyncio.ensure_future(c),多工則需建立tasks列表,把task append到tasks裡。loop.run_until_complete(asyncio.wait(tasks))
建立事件迴圈物件loop=asyncio.get_event_loop()
將任務物件裝載在事件迴圈物件中啟動事件迴圈物件loop.run_until_complete(task)
如果函式有返回值,則給任務物件繫結回撥函式task.add_done_callback(task_callback)
回撥函式必須有一個引數,data=引數.result(),data則為函式的返回值
await關鍵字:掛起發⽣阻塞操作的任務物件。 在任務物件表示的操作中,凡是阻塞操作的前⾯ 都必須加上await關鍵字進⾏修飾
如果用協程去獲取網路請求就需要用到:aiohttp模組,aiohttp支援非同步模組
在每⼀個with前加上async關鍵字
在阻塞操作前加上await關鍵字
async def get_request(url): #requests是不⽀持非同步的模組 # response = await requests.get(url=url) # page_text = response.text #建立請求物件(sess) async with aiohttp.ClientSession() as sess: #基於請求物件發起請求 #此處的get是發起get請求,常⽤引數:url,headers,params,proxy #post⽅法發起post請求,常⽤引數:url,headers,data,proxy #發現處理代理的引數和requests不⼀樣(注意),此處處理代理使⽤proxy='http://ip:port' 多工非同步爬⾍的完整程式碼實現: async with await sess.get(url=url) as response: page_text = await response.text() #text():獲取字串形式的響應資料 #read():獲取⼆進位制形式的響應資料 return page_text
第三方庫模組:gevent模組
用到的函式:f=gevent.spawn(func,argv)(生成協程函式)gevent.joinall(list,[timeout])
匯入monkey模組 from gevent import monkey
執行相應指令碼 monkey.patch_socket()(很多指令碼,需要自己選擇自己需要的,或者patch_all())
指令碼執行要在相應的模組匯入之前
""" gevent server 基於協成的tcp併發 思路 : 1. 每個客戶函式端設定為協成 2. 將socket模組下的阻塞變為可以觸發協程跳轉 """ import gevent from gevent import monkey monkey.patch_all() # 執行指令碼,修改socket from socket import * def handle(c): while True: data = c.recv(1024).decode() if not data: break print(data) c.send(b'OK') c.close() # 建立tcp套接字 s = socket() s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('0.0.0.0',8888)) s.listen(5) # 迴圈接收來自客戶端連線 while True: c,addr = s.accept() print("Connect from",addr) # handle(c) # 處理具體客戶端請求 gevent.spawn(handle,c) # 協程方案
人工智慧(PythonNet)—— 目錄彙總