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 添加用户设置密码并发邮件给root用户
Jul 25 Python
Python中操作mysql的pymysql模块详解
Sep 13 Python
python getopt详解及简单实例
Dec 30 Python
利用python将xml文件解析成html文件的实现方法
Dec 22 Python
快速了解Python中的装饰器
Jan 11 Python
Python实现找出数组中第2大数字的方法示例
Mar 26 Python
DataFrame中去除指定列为空的行方法
Apr 08 Python
pandas全表查询定位某个值所在行列的方法
Apr 12 Python
Python的Tkinter点击按钮触发事件的例子
Jul 19 Python
Win10里python3创建虚拟环境的步骤
Jan 31 Python
python游戏开发的五个案例分享
Mar 09 Python
Pytorch自定义Dataset和DataLoader去除不存在和空数据的操作
Mar 03 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
自制短波长线天线频率预选器 - 成功消除B2K之流的镜像
2021/03/02 无线电
php 修改zen-cart下单和付款流程以防止漏单
2010/03/08 PHP
微盾PHP脚本加密专家php解密算法
2020/09/13 PHP
php中配置文件操作 如config.php文件的读取修改等操作
2012/07/07 PHP
php的一个简单加密解密代码
2014/01/14 PHP
Codeigniter检测表单post数据的方法
2015/03/21 PHP
javascript之卸载鼠标事件的代码
2007/05/14 Javascript
js 中 document.createEvent的用法
2010/08/29 Javascript
java与javascript之间json格式数据互转介绍
2013/10/29 Javascript
node.js应用后台守护进程管理器Forever安装和使用实例
2014/06/01 Javascript
浅谈window对象的scrollBy()方法
2015/07/15 Javascript
基于jQuery实现搜索关键字自动匹配功能
2020/03/26 Javascript
AngularJS路由Ui-router模块用法示例
2017/05/29 Javascript
基于滚动条位置判断的简单实例
2017/12/14 Javascript
详解React中setState回调函数
2018/06/14 Javascript
JS闭包经典实例详解
2018/12/20 Javascript
微信小程序保存多张图片的实现方法
2019/03/05 Javascript
angular使用md5,CryptoJS des加密的方法
2019/06/03 Javascript
layui在form表单页面通过Validform加入简单验证的方法
2019/09/06 Javascript
JQuery使用属性addClass、removeClass和toggleClass实现增加和删除类操作示例
2019/11/18 jQuery
Python实现二分查找算法实例
2015/05/26 Python
Python通过调用mysql存储过程实现更新数据功能示例
2018/04/03 Python
Python使用selenium实现网页用户名 密码 验证码自动登录功能
2018/05/16 Python
python使用opencv在Windows下调用摄像头实现解析
2019/11/26 Python
Python2与Python3的区别点整理
2019/12/12 Python
安装PyInstaller失败问题解决
2019/12/14 Python
Tensorflow分批量读取数据教程
2020/02/07 Python
从0到1使用python开发一个半自动答题小程序的实现
2020/05/12 Python
基于HTML5+CSS3实现简单的时钟效果
2017/09/11 HTML / CSS
详解如何解决H5开发使用wx.hideMenuItems无效果不生效
2021/01/20 HTML / CSS
彪马美国官网:PUMA美国
2017/03/09 全球购物
求职信需要的五点内容
2014/02/01 职场文书
2015年税务稽查工作总结
2015/05/26 职场文书
励志正能量20句:送给所有为梦想拼搏的人
2019/11/11 职场文书
基于CSS3画一个iPhone
2021/04/21 HTML / CSS
详解python网络进程
2021/06/15 Python