Python中asyncio与aiohttp入门教程


Posted in Python onOctober 16, 2018

很多朋友对异步编程都处于“听说很强大”的认知状态。鲜有在生产项目中使用它。而使用它的同学,则大多数都停留在知道如何使用 Tornado、Twisted、Gevent 这类异步框架上,出现各种古怪的问题难以解决。而且使用了异步框架的部分同学,由于用法不对,感觉它并没牛逼到哪里去,所以很多同学做 Web 后端服务时还是采用 Flask、Django等传统的非异步框架。

从上两届 PyCon 技术大会看来,异步编程已经成了 Python 生态下一阶段的主旋律。如新兴的 Go、Rust、Elixir 等编程语言都将其支持异步和高并发作为主要“卖点”,技术变化趋势如此。Python 生态为不落人后,从2013年起由 Python 之父 Guido 亲自操刀主持了Tulip(asyncio)项目的开发。

异步io的好处在于避免的线程的开销和切换,而且我们都知道python其实是没有多线程的,只是通过底层线层锁实现的多线程。另一个好处在于避免io操作(包含网络传输)的堵塞时间。

asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。如果把asyncio用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持。

asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架。

  • 对于异步io你需要知道的重点,要注意的是,await语法只能出现在通过async修饰的函数中,否则会报SyntaxError错误。而且await后面的对象需要是一个Awaitable,或者实现了相关的协议。

注意:

  • 所有需要异步执行的函数,都需要asyncio中的轮训器去轮训执行,如果函数阻塞,轮训器就会去执行下一个函数。所以所有需要异步执行的函数都需要加入到这个轮训器中。

asyncio

asyncio的基本概念asyncio是在python3.4中被引进的异步IO库。你也可以通过python3.3的pypi来安装它。它相当的复杂,而且我不会介绍太多的细节。相反,我将会解释你需要知道些什么,以利用它来写异步的代码。简而言之,有两件事情你需要知道:协同程序和事件循环。协同程序像是方法,但是它们可以在代码中的特定点暂停和继续。当在等待一个IO(比如一个HTTP请求),同时执行另一个请求的时候,可以用来暂停一个协同程序。

例如:

import requests
import time
import asyncio
# 创建一个异步函数
async def task_func():
  await asyncio.sleep(1)
  resp = requests.get('http://192.168.2.177:5002/')
  print('2222222',time.time(),resp.text)
async def main(loop):
  loop=asyncio.get_event_loop()  # 获取全局轮训器
  task = loop.create_task(task_func()) # 在全局轮训器加入协成,只有加入全局轮训器才能被监督执行
  await asyncio.sleep(2)  # 等待两秒为了不要立即执行event_loop.close(),项目中event_loop应该是永不停歇的
  print('11111111111',time.time())
event_loop = asyncio.get_event_loop()
try:
  event_loop.run_until_complete(main(event_loop))
finally:
  event_loop.close()  # 当轮训器关闭以后,所有没有执行完成的协成将全部关闭

aiohttp服务器

下面是aiohttp作为服务器端的一个简单的demo。

#!/usr/bin/env python3
import argparse
from aiohttp import web
import asyncio
import base64
import logging
import uvloop
import time,datetime
import json
import requests
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
routes = web.RouteTableDef()
@routes.get('/')
async def hello(request):
  return web.Response(text="Hello, world")
# 定义一个路由映射,接收网址参数,post方式
@routes.post('/demo1/{name}')
async def demo1(request):  # 异步监听,只要一有握手就开始触发,此时网址参数中的name就已经知道了,但是前端可能还没有完全post完数据。
  name = request.match_info.get('name', "Anonymous") # 获取name
  print(datetime.datetime.now())  # 触发视图函数的时间
  data = await request.post()  # 等待post数据完成接收,只有接收完成才能进行后续操作.data['key']获取参数
  print(datetime.datetime.now())  # 接收post数据完成的时间
  logging.info('safety dect request start %s' % datetime.datetime.now())
  result = {'name':name,'key':data['key']}
  logging.info('safety dect request finish %s, %s' % (datetime.datetime.now(),json.dumps(result)))
  return web.json_response(result)
# 定义一个路由映射,设计到io操作
@routes.post('/demo2')
async def demo2(request):  # 异步监听,只要一有握手就开始触发,此时网址参数中的name就已经知道了,但是前端可能还没有完全post完数据。
  data = await request.post()  # 等待post数据完成接收,只有接收完成才能进行后续操作.data['key']获取参数
  logging.info('safety dect request start %s' % datetime.datetime.now())
  res = requests.post('http://www.baidu.com')  # 网路id,会自动切换到其他协成上
  logging.info('safety dect request finish %s' % res.test)
  return web.Response(text="welcome")
if __name__ == '__main__':
  logging.info('server start')
  app = web.Application()
  app.add_routes(routes)
  web.run_app(app,host='0.0.0.0',port=8080)
  logging.info('server close')

aiohttp客户端

aiohttp的另一个主要作用是作为异步客户端,用来解决高并发请求的情况。比如现在我要模拟一个高并发请求来测试我的服务器负载情况。所以需要在python里模拟高并发。高并发可以有多种方式,比如多线程,但是由于python本质上是没有多线程的,通过底层线程锁实现的多线程。在模型高并发时,具有线程切换和线程开销的损耗。所以我们就可以使用多协成来实现高并发。

我们就可以使用aiohttp来模拟高并发客户端。demo如下,用来模拟多个客户端向指定服务器post图片。

# 异步并发客户端
class Asyncio_Client(object):
  def __init__(self):
    self.loop=asyncio.get_event_loop()
    self.tasks=[]
  # 将异步函数介入任务列表。后续参数直接传给异步函数
  def set_task(self,task_fun,num,*args):
    for i in range(num):
      self.tasks.append(task_fun(*args))
  # 运行,获取返回结果
  def run(self):
    back=[]
    try:
      f = asyncio.wait(self.tasks)  # 创建future
      self.loop.run_until_complete(f) # 等待future完成
    finally:
      pass
# 服务器高并发压力测试
class Test_Load():
  total_time=0 # 总耗时
  total_payload=0 # 总负载
  total_num=0 # 总并发数
  all_time=[]
  # 创建一个异步任务,本地测试,所以post和接收几乎不损耗时间,可以等待完成,主要耗时为算法模块
  async def task_func1(self,session):
    begin = time.time()
    # print('开始发送:', begin)
    file=open(self.image, 'rb')
    fsize = os.path.getsize(self.image)
    self.total_payload+=fsize/(1024*1024)
    data = {"image_id": "2", 'image':file}
    r = await session.post(self.url,data=data) #只post,不接收
    result = await r.json()
    self.total_num+=1
    # print(result)
    end = time.time()
    # print('接收完成:', end,',index=',self.total_num)
    self.all_time.append(end-begin)
  # 负载测试
  def test_safety(self):
    print('test begin')
    async_client = Asyncio_Client() # 创建客户端
    session = aiohttp.ClientSession()
    for i in range(10): # 执行10次
      self.all_time=[]
      self.total_num=0
      self.total_payload=0
      self.image = 'xxxx.jpg' # 设置测试nayizhang
      print('测试图片:', self.image)
      begin = time.time()
      async_client.set_task(self.task_func1,self.num,session) # 设置并发任务
      async_client.run()  # 执行任务
      end=time.time()
      self.all_time.sort(reverse=True)
      print(self.all_time)
      print('并发数量(个):',self.total_num)
      print('总耗时(s):',end-begin)
      print('最大时延(s):',self.all_time[0])
      print('最小时延(s):', self.all_time[len(self.all_time)-1])
      print('top-90%时延(s):', self.all_time[int(len(self.all_time)*0.1)])
      print('平均耗时(s/个):',sum(self.all_time)/self.total_num)
      print('支持并发率(个/s):',self.total_num/(end-begin))
      print('总负载(MB):',self.total_payload)
      print('吞吐率(MB/S):',self.total_payload/(end-begin))  # 吞吐率受上行下行带宽,服务器带宽,服务器算法性能诸多影响
      time.sleep(3)
    session.close()
    print('test finish')

aiohttp服务器mvc(静态网页,模板,数据库,log)

aiohttp之添加静态资源路径

所谓静态资源,是指图片、js、css等文件。

以一个小项目来说明,下面是项目的目录结构:

.
├── static
│  ├── css
│  │  ├── base.css
│  │  ├── bootstrap.min.css
│  │  └── font-awesome.min.css
│  ├── font
│  │  ├── FontAwesome.otf
│  │  ├── fontawesome-webfont.eot
│  │  ├── fontawesome-webfont.svg
│  │  ├── fontawesome-webfont.ttf
│  │  └── fontawesome-webfont.woff
│  └── index.html
└── proxy_server.py

在proxy_server.py给2个静态文件目录static/css和static/font添加路由:

app.router.add_static('/css/',
            path='static/css',
            name='css')
app.router.add_static('/font/',
            path='static/font',
            name='font')

必需的2个参数:

prefix:是静态文件的url的前缀,以/开始,在浏览器地址栏上显示在网站host之后,也用于index.html静态页面进行引用
path:静态文件目录的路径,可以是相对路径,上面代码使用的static/css就是相对路径——相对于proxy_server.py所在路径。

Python中asyncio与aiohttp入门教程

加载的是index.html,下面是它引用静态资源的代码:

<!-- Bootstrap CSS -->
<link href="css/bootstrap.min.css" rel="external nofollow" rel="stylesheet">
<!-- Base CSS -->
<link href="css/base.css" rel="external nofollow" rel="stylesheet">
<!-- FA CSS -->
<link href="css/font-awesome.min.css" rel="external nofollow" rel="stylesheet">

添加font的路径是因为/font-awesome.min.css需要使用:

如果修改前缀:

app.router.add_static('/css2017/',
            path='static/css',
            name='css')

虽然目录本身还是css,但通过add_static已经将它视为了css2017,在文件和浏览器中要想链接到css下的文件,必须使用css2017/xx.css来链接。

此外,如果加上show_index=True,就可以显示静态资源的目录索引了——默认是禁止访问的:

app.router.add_static('/css2017/',
            path='static/css',
            name='css',
            show_index=True)

Python中asyncio与aiohttp入门教程

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。如果你想了解更多相关内容请查看下面相关链接

Python 相关文章推荐
详解Python的Django框架中的Cookie相关处理
Jul 22 Python
Python实现线程状态监测简单示例
Mar 28 Python
使用Python监视指定目录下文件变更的方法
Oct 15 Python
Python Numpy库安装与基本操作示例
Jan 08 Python
分析经典Python开发工程师面试题
Apr 08 Python
Python实现的栈、队列、文件目录遍历操作示例
May 06 Python
python实践项目之监控当前联网状态详情
May 23 Python
Python之虚拟环境virtualenv,pipreqs生成项目依赖第三方包的方法
Jul 23 Python
Django用户认证系统 Web请求中的认证解析
Aug 02 Python
基于spring boot 日志(logback)报错的解决方式
Feb 20 Python
解决Pycharm双击图标启动不了的问题(JetBrains全家桶通用)
Aug 07 Python
Python经纬度坐标转换为距离及角度的实现
Nov 01 Python
python查看模块安装位置的方法
Oct 16 #Python
Django model序列化为json的方法示例
Oct 16 #Python
Python重新加载模块的实现方法
Oct 16 #Python
django Serializer序列化使用方法详解
Oct 16 #Python
为什么str(float)在Python 3中比Python 2返回更多的数字
Oct 16 #Python
对python添加模块路径的三种方法总结
Oct 16 #Python
Python中的CSV文件使用&quot;with&quot;语句的方式详解
Oct 16 #Python
You might like
php常用Stream函数集介绍
2013/06/24 PHP
浅谈php中urlencode与rawurlencode的区别
2016/09/05 PHP
Javascript 布尔型分析
2008/12/22 Javascript
基于jquery的滑动样例代码
2010/11/20 Javascript
jquery实现效果比较好的table选中行颜色
2014/03/25 Javascript
jquery使用ajax实现微信自动回复插件
2014/04/28 Javascript
vue的全局提示框组件实例代码
2018/02/26 Javascript
JavaScript实现图片懒加载的方法分析
2018/07/05 Javascript
Vue常用指令详解分析
2018/08/19 Javascript
解决angularjs WdatePicker ng-model的问题
2018/09/13 Javascript
浅谈微信页面入口文件被缓存解决方案
2018/09/29 Javascript
浅析微信扫码登录原理(小结)
2018/10/29 Javascript
vue-cli 构建骨架屏的方法示例
2018/11/08 Javascript
[29:23]2014 DOTA2国际邀请赛中国区预选赛 LGD-GAMING VS CIS 第一场1
2014/05/23 DOTA
[41:12]Liquid vs Secret 2019国际邀请赛淘汰赛 败者组 BO3 第一场 8.24
2019/09/10 DOTA
[37:45]完美世界DOTA2联赛PWL S3 LBZS vs Phoenix 第二场 12.09
2020/12/11 DOTA
使用grappelli为django admin后台添加模板
2014/11/18 Python
详解Python3操作Mongodb简明易懂教程
2017/05/25 Python
Python使用progressbar模块实现的显示进度条功能
2018/05/31 Python
对python中Librosa的mfcc步骤详解
2019/01/09 Python
Pytorch加载部分预训练模型的参数实例
2019/08/18 Python
淘宝秒杀python脚本 扫码登录版
2019/09/19 Python
python实现交并比IOU教程
2020/04/16 Python
python爬取代理IP并进行有效的IP测试实现
2020/10/09 Python
Python基于内置函数type创建新类型
2020/10/22 Python
实例讲解HTML5的meta标签的一些应用
2015/12/08 HTML / CSS
美国受信赖的教育产品供应商:Nest Learning
2018/06/14 全球购物
阿巴庭院:Abba Patio
2019/06/18 全球购物
Waterford英国官方网站:世界上最受欢迎的优质水晶品牌
2019/08/17 全球购物
大学社团计划书
2014/05/01 职场文书
环卫工人慰问信
2015/02/15 职场文书
百年孤独读书笔记
2015/06/29 职场文书
校运会加油稿大全
2015/07/22 职场文书
少儿励志名言(80句)
2019/08/14 职场文书
Mysql数据库索引面试题(程序员基础技能)
2021/05/31 MySQL
用Python生成会跳舞的美女
2022/01/18 Python