python学习笔记-异步IO模块的使用
一、使用twisted
对python3不是完全的支持,安装:pip install twisted
twisted模块使用例子
from twisted.internet import reactor from twisted.web.client import Agent,readBody from twisted.internet import defer def one_done(arg): print(arg) def all_done(arg): print('done') reactor.stop() @defer.inlineCallbacks def task(url): agent = Agent(reactor) ret = agent.request(b'GET', url.encode('utf-8')) ret.addCallback(one_done) yield ret url_list=[ 'https://www.python.org/', 'https://www.yahoo.com/' ] defer_list=[] for url in url_list: v=task(url) defer_list.append(v) d=defer.DeferredList(defer_list) d.addBoth(all_done) reactor.run()
twisted其他详细使用,可看之前的文章:scrapy源码流程和自定义爬虫框架
二、使用Tornado
案例代码
from tornado.httpclient import HTTPRequest,AsyncHTTPClient from tornado import ioloop def handle_response(response): if response.error: print("Error:",response.error) else: print(response.body) def func(): url_list = [ 'https://www.python.org/', 'https://www.yahoo.com/' ] for url in url_list: print(url) http_client=AsyncHTTPClient() http_client.fetch(HTTPRequest(url),handle_response) ioloop.IOLoop.current().add_callback(func) ioloop.IOLoop.current().start() #是一个死循环
*执行有错误提示,403,Future exception was never retrieved,版本问题
要正确结束循环,可以加个计数器
from tornado.httpclient import HTTPRequest,AsyncHTTPClient from tornado import ioloop COUNT=0 def handle_response(response): global COUNT COUNT -= 1 if response.error: print("Error:",response.error) else: print(response.body) if COUNT == 0: ioloop.IOLoop.current().stop() def func(): url_list = [ 'https://www.python.org/', 'https://www.baidu.com/' ] global COUNT COUNT=len(url_list) for url in url_list: print(url) http_client=AsyncHTTPClient() http_client.fetch(HTTPRequest(url),handle_response) ioloop.IOLoop.current().add_callback(func) ioloop.IOLoop.current().start() #是一个死循环
*python3.1版本执行未结束循环
结合前面的一篇文章,学习了四个异步IO模块。实际使用中一般不用自己编写,用已有现成的框架
多种写法的比较
====>gevent>Twisted>Tornado>asyncio
三、自定义异步IO模块
1、自定义异步IO模块前了解流程
1、socket客户端,服务端
连接阻塞
setblocking(0),设置0或false后socket不会阻塞,没数据(连接无响应,数据未返回)就会报错,
2.IO多路复用
客户端:
try:
socket对象1.connet()
socket对象1.connet()
socket对象1.connet()
except Ex:
pass
while True:
r,w,e=select.select([socket对象1,socket对象2,socket对象3],[socket对象1,socket对象2,socket对象3],[],0.05)
r=[socket对象1,] #有数据时,表示有人给我发送数据
socket对象1.recv()
w=[socket对象1,] #W有数据时,表示我已经和别人创建连接成功
socket对象1.send('...')
3、select内部列表中是否只能写socket对象,不是
r,w,e=select.select([socket对象?,socket对象?,socket对象?],[],[])
#对象必须有fileno方法,并返回一个文件描述符
class Foo:
def fileno(self):
obj=socket()
return obj.fileno()
r,w,e=select.select([socket对象?,socket对象?,socket对象?,Foo()],[],[])
=======
a.select内部:对象.fileno()
b.Foo()内部封装socket文件描述符
2、发送Http请求的原理、本质
案例1:
import socket import select sk=socket.socket() #连接 sk.connect(('www.baidu.com',80,)) #这步骤是阻塞的 print('连接成功了。。。') #发送请求 sk.send(b'GET / HTTP/1.0\r\nHost:www.baidu.com\r\n\r\n') #sk.send(b'POST / HTTP/1.0\r\nHost:www.baidu.com\r\n\r\nk1=v1&k2=v2') #post请求的写法 #等待服务端响应 data=sk.recv(8096) #阻塞的 print(data) #关闭连接 sk.close()
执行结果:

3、自定义异步IO框架
案例2:非阻塞的
import socket import select ####自定义异步IO框架 class HttpRequest: def __init__(self,sk,host,callback): self.socket=sk self.host=host self.callback=callback def fileno(self): return self.socket.fileno() class AsyncRequest: def __init__(self): self.conn=[] self.connection=[] #用户检查是否已经连接成功 def add_request(self,host,callback): try: sk=socket.socket() sk.setblocking(0) sk.connect((host,80,)) except BlockingIOError as e: pass request=HttpRequest(sk,host,callback) self.conn.append(request) self.connection.append(request) def run(self): while True: rlist,wlist,elist=select.select(self.conn,self.connection,self.conn,0.05) for w in wlist: print(w.host,'连接成功') #只有能循环到,表示socket与服务器已经连接成功 tpl='GET / HTTP/1.0\r\nHost:%s\r\n\r\n'%(w.host) w.socket.send(bytes(tpl,encoding='utf-8')) self.connection.remove(w) for r in rlist: #r是HttpRequest对象 recv_data=bytes() while True: try: chunck=r.socket.recv(8096) recv_data+=chunck except Exception as e: break #print(r.host,'返回数据..',recv_data) r.callback(recv_data) r.socket.close() self.conn.remove(r) if len(self.conn)==0: break def f1(data): print('保存到文件',data) def f2(data): print('保存到数据库',data) def f3(data): print('保存到数据库',data) url_list=[ {'host':'www.baidu.com','callback':f1}, {'host':'cn.bing.com','callback':f2}, {'host':'www.cnblogs.com','callback':f3} ] req=AsyncRequest() for item in url_list: req.add_request(item['host'],item['callback']) req.run()
以上代码可以继续完善,如增加响应头和响应体的处理
获取响应的头和响应体
headers,body=recv_data.split(b'\r\n\r\n',1) header_list=headers.split(b'\r\n')
封装成对象处理
class HttpResponse: def __init__(self,recv_data): self.recv_data=recv_data self.header_dict={} self.body=None self.initialize() def initialize(self): headers,body=self.recv_data.split(b'\r\n\r\n',1) self.body=body header_list=headers.split(b'\r\n') for h in header_list: h_str=str(h,encoding='utf-8') v=h_str.split(':',1) if len(v)==2: self.header_dict[v[0]]=v[1]
总结:
异步就是回调
IO多路复用:r,w,e=while 就是监听多个socket对象,利用其特性可以做很多操作
异步IO:非阻塞的socket+IO多路复用
-非阻塞socket
-select[自己封装的对象],w,r
浙公网安备 33010602011771号