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()
View Code

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()   #是一个死循环
View Code

*执行有错误提示,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()   #是一个死循环
View Code

*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()

执行结果:

image

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()
View Code

 以上代码可以继续完善,如增加响应头和响应体的处理

获取响应的头和响应体

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

posted @ 2026-01-18 17:24  子不语332  阅读(2)  评论(0)    收藏  举报