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类的__getitem__和__setitem__特殊方法
Dec 25 Python
Python 装饰器使用详解
Jul 29 Python
python实现图片彩色转化为素描
Jan 15 Python
python实现贪吃蛇游戏
Mar 21 Python
Python实现DDos攻击实例详解
Feb 02 Python
python ChainMap的使用和说明详解
Jun 11 Python
三步实现Django Paginator分页的方法
Jun 11 Python
pyqt 实现在Widgets中显示图片和文字的方法
Jun 13 Python
Python爬虫抓取技术的一些经验
Jul 12 Python
Python 、Pycharm、Anaconda三者的区别与联系、安装过程及注意事项
Oct 11 Python
python 计算积分图和haar特征的实例代码
Nov 20 Python
基于python调用psutil模块过程解析
Dec 20 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字符串分割函数explode的实例代码
2013/02/07 PHP
PHP中调用C/C++制作的动态链接库的教程
2016/03/10 PHP
php+redis在实际项目中HTTP 500: Internal Server Error故障排除
2017/02/05 PHP
javascript之可拖动的iframe效果代码
2008/08/01 Javascript
Div Select挡住的解决办法
2008/08/07 Javascript
JS获取父节点方法
2009/08/20 Javascript
JS在IE和FF下attachEvent,addEventListener学习笔记
2009/11/26 Javascript
JS中getYear()和getFullYear()区别分析
2014/07/04 Javascript
JS实现仿微博可关闭弹出层效果
2015/09/21 Javascript
js实现图片上传并正常显示
2015/12/19 Javascript
javascript断点调试心得分享
2016/04/23 Javascript
JS组件Bootstrap按钮组与下拉按钮详解
2016/05/10 Javascript
AngularJS学习笔记之依赖注入详解
2016/05/16 Javascript
jQuery中的insertBefore(),insertAfter(),after(),before()区别介绍
2016/09/01 Javascript
js获取一组日期中最近连续的天数
2017/05/25 Javascript
vue mounted组件的使用
2018/06/18 Javascript
js中let能否完全替代IIFE
2019/06/15 Javascript
微信小程序实现搜索功能
2020/03/10 Javascript
jdk1.8+vue elementui实现多级菜单功能
2020/09/24 Javascript
Nuxt的路由配置和参数传递方式
2020/11/06 Javascript
详解Python使用simplejson模块解析JSON的方法
2016/03/24 Python
Python3.X 线程中信号量的使用方法示例
2017/07/24 Python
python多进程提取处理大量文本的关键词方法
2018/06/05 Python
浅析python中的迭代与迭代对象
2018/10/08 Python
浅谈SciPy中的optimize.minimize实现受限优化问题
2020/02/29 Python
pycharm新建Vue项目的方法步骤(图文)
2020/03/04 Python
html5小程序飞入购物车(抛物线绘制运动轨迹点)
2020/10/19 HTML / CSS
Troy-Bilt官网:草坪割草机、吹雪机、分蘖机等
2019/02/19 全球购物
巴西24小时在线药房:Droga Raia
2020/05/12 全球购物
土木工程毕业生推荐信
2013/10/28 职场文书
电子信息专业自荐书
2014/02/04 职场文书
篮球兴趣小组活动总结
2014/07/07 职场文书
医院2014国庆节活动策划方案
2014/09/21 职场文书
四风批评与自我批评发言稿
2014/10/14 职场文书
售后服务承诺函格式
2015/01/21 职场文书
请客吃饭开场白
2015/06/01 职场文书