django3,websocket,asgi,daphne,nginx 结合使用示例

Posted 小玉的小本本

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了django3,websocket,asgi,daphne,nginx 结合使用示例相关的知识,希望对你有一定的参考价值。

这篇文章主要讲解在 django3 的项目中,如何使用 websocket

这是一个 代理IP 的小项目,当前为0.2版本,该版本分为三端,服务器端,代理IP端,用户端

可以做到在任意一台服务器和任意内网电脑上一键部署,一键启动,使该服务器通过内网机器为你提供代理IP服务。可以做到在服务器数量有限的情况下,部署更多的代理IP

该项目今后的发展方向是在手机上部署代理IP端,以获得更多的代理IP

当前环境为:

ubuntu 20.04
python 3.8
aiohttp
asyncio
django 3.1
daphne
nginx

项目流程图:

看起来很简单的4个步骤,在技术选型上却想了很久,曾经想过轮询和消息队列的模型,都遇见一个无法绕过的问题:服务器和用户连接的这个进程,如何才能知道代理IP那边已经完成了任务?

后来突发奇想,能不能让TCP的连接不断开,有这个保证的话,可能会想到一些办法。我就上百度去搜,发现这个技术已经有了,就是websocket,这名字我是见过的,在我爬取招聘网站信息提取关键词的时候,我还以为那是socket的一种别称,就没注意过它……

这里,我主要先讲一下

websocket 如何与采用 asgi 协议的 django3.X 项目结合食用.

如果asgi.py文件还不会写,那么请先看最下面的  【asgi.py 文件的编写】,那个不是我的原创,就放在后面了



websocket 的使用在 django3 里面有3个参数,他们分别是:(scope, receive, send)

async def application(scope, receive, send):
   """
  var connection = new WebSocket('ws://127.0.0.1:8000/abc')
   
  scope: <class 'dict'> 详细如下
      {'type': 'websocket',
      'path': '/abc',
      'raw_path': b'/abc',
      'headers': [(b'host', b'127.0.0.1:8000'), (b'upgrade', b'WebSocket'), (b'connection', b'Upgrade'),
                  (b'sec-websocket-version', b'13'), (b'sec-websocket-key', b'2viLa4ZBnF2953ARONVITw=='),
                  (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'user-agent', b'Python/3.8 aiohttp/3.6.2')],
      'query_string': b'',
      'client': ['127.0.0.1', 47220],
      'server': ['127.0.0.1', 8000],
      'subprotocols': [],
      'asgi': {'version': '3.0'}}

  receive: <class 'method'> 详细如下
      <bound method Queue.get of <Queue at 0x7f92b14df5b0 maxsize=0 _queue=[{'type': 'websocket.connect'}] tasks=1>>
       

  send: <class 'function'>

            

  """


从上面的注解可以看出:

scope 是一个详细信息的字典,其中包含了(协议类型,请求路径,请求头,查询字符串,客户端IP,服务端IP和其他的东西)

receive 是一个接收消息的对象,很明显可以看到其中包含了一条管道对象,而这条管道,我理解为就是收发消息的通道

send 是一个发送消息的对象,里面也包含了这条管道。

每一个建立的 websocket 都拥有独一无二的 这三个参数,也就是说,这三个参数合在一起,就相当于是我们建立的 websocket,

这也是当初困扰我的问题,如何在一个websocket里面,向另一个websocket发送消息,其实我们只需要拿到另一个websocket的 send 参数就可以了。

接下来,我们只要在连接建立之后,将这三个参数存入一个全局变量中,或是数据库中,我们就可以在任何地方,任何时候,调用任意一个websocket,以实现我们由服务端,主动向客户端发送消息的需求

# 这里展示一下,在代理IP与服务器建立的websocket连接中,调用客户与服务器连接的send对象,向客户发送消息
pmip_dict = {}

async def bypmip(scope, receive, send):
   """
  服务器端逻辑:将代理IP的IP做键,websocket对象作值,存入pmip_dict字典,如果该键已经存在,则拒绝连接,断开连接时删除该键值对
  循环等待接收代理IP服务器的消息(这个消息包含爬虫返回的信息在内),再将该消息转发给提出此需求的客户
  """
   while True:
       event = await receive()

       if event['type'] == 'websocket.connect':
           # 初次建立连接时,会进入这个分支
           ip = scope['client'][0]
           if not pmip_dict.get(ip, ''):
               # 将代理IP的IP做键,整个websocket作为值,存入字典
               pmip_dict[ip] = [scope, receive, send]
               await send({'type': 'websocket.accept'})
           else:
               await send({'type': 'websocket.no'})
               
       elif event['type'] == 'websocket.receive':
           # 连接建立以后,接收到的消息会进入这个分支
           # 判断这条消息是给哪个客户的,进行选择发送
           # print(event)
           # >>> {'type': 'websocket.receive', 'bytes': b'user=127.0.0.1:40766,text=\n<!doctype html>\n\n<html>\n…………}
           message = event['bytes']
           # >>> b'user=127.0.0.1:40766,text=\n<!doctype html>\n\n<html>\n…………
           user = re.search(rb'user=(?P<IP>.*?),text', message, re.S).group('IP').decode()
           text = re.search(rb',text=(?P<text>.*)',message,re.S).group('text')
           if target_send := clien_dict.get(user,None)[-1]:
               # 【这里就是在代理IP与服务器的websocket连接中,调用客户与服务器的send对象,向客户发送消息】
               await target_send({'type': 'websocket.send', 'bytes': text})

       elif event['type'] == 'websocket.disconnect':
           # 断开连接时,会进入这个分支
           try:
               del pmip_dict[ip]
           except Exception as e:
               pass
           print('断开连接')
           break



asgi.py 文件的编写(这里是百度上找的)

import os

from django.core.asgi import get_asgi_application

# 【【【【记得改】】】】
from [你的项目名字].websocket import websocket_application
# 【【【【记得改】】】】

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myasgi.settings')

django_application = get_asgi_application()


async def application(scope, receive, send):
   if scope['type'] == 'http':
       # http 请求的入口
       await django_application(scope, receive, send)
   elif scope['type'] == 'websocket':
       # websocket 请求的入口
       await websocket_application(scope, receive, send)
   else:
       raise NotImplementedError(f"Unknown scope type {scope['type']}")


接着在 asgi.py 文件的同级目录下,建立一个 websocket.py文件,并在里面创建一个函数 websocket_application,这个函数里就是整个websocket 完整逻辑

async def websocket_application(scope, receive, send):
   # 主函数
   # 这里面是你自己的逻辑,怎么写都行
   
   # 我这里是鉴别来者身份,并执行对应函数
   result = await authentication(scope)
   await method_dict[result](scope, receive, send)


用 daphne 启动项目

# 进入项目目录下
daphne [你的项目名字].asgi:application

基于 nginx 的 daphne(百度找的,亲测可用)

修改nginx 的配置文件 /etc/nginx/sites-enabled/default,添加或修改如下信息


upstream socket {
  ip_hash;
  server 127.0.0.1:8000 fail_timeout=0;
}
# 上面的IP端口自定义,就是daphne运行的端口
server {
listen 80 default_server;
  listen [::]:80 default_server;

  ...

  location / {
      proxy_pass http://socket;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
  }

  ...
}

启动daphne

# 在项目的 mannerge.py 同级目录下执行
daphne [你的项目名字].asgi:application

到这里,整个项目就启动了,欢迎点赞分享



欢迎关注我的微信公众号:不定期更新一些python相关的文章




以上是关于django3,websocket,asgi,daphne,nginx 结合使用示例的主要内容,如果未能解决你的问题,请参考以下文章

预期的 ASGI 消息“websocket.accept”或“websocket.close”,但得到“http.response.start”

Django 只能处理 ASGI/HTTP 连接,不能处理 websocket

Django3 中遇到django.core.exceptions.ImproperlyConfigured mysqlclient 1.3.13 or newer is required; you

Django3 使用 WebSocket 实现 WebShell

Django 通过 websocket 向所有客户端发送事件

Django 现在在 Heroku 中使用 ASGI + WSGI 的频道