最近做了一个项目,需要后端主动与前端进行通信,官方推荐使用的是channels(channels官方文档). 把我的使用经历以及部署过程,记录一下,这篇文章主要记录使用 uwsgi, nginx, supervisor,daphne部署过程,有时间的会把我用docker 部署的经历也写一下.
安装channels,使用websocket
- 下载channels
pip install channels
- 项目中配置channels ,在
settings
文件的INSTALLED_APPS
加入channels
- 在项目中新建
routing.py
文件,用来生成websocket的路由,这里我的文件和官方的不太一样,但是最终的效果是一样的,就像我们平时写urls.py
的路由一样,一个在应用目录下,一个在项目下.我把routing.py
建在应用目录下.
(1) 这是我的项目的目录结构,以及我的routing.py
文件的位置
(2) 这是routing.py
文件的内容,具体写什么,先空着,一步一步来完善
from django.urls import path
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from struction.consumers import SendConsumer
application = ProtocolTypeRouter({
# WebSocket chat handler
"websocket": AuthMiddlewareStack(
URLRouter([
])
),
})
(3) routing.py
文件创建完成之后,在settings.py
进行配置
#websocket
ASGI_APPLICATION = "struction.routing.application" # 上面新建的 asgi 应用
- 然后在
struction
应用下新建一个consumers.py
文件用来写websocket通信的业务逻辑
官方文档中的案例是做一个聊天室,我实际的项目需求只需要给前端发消息,不需要做到官方文档中那样,所以在websocket的通信这部分做了修改.
import json
import time
from channels.generic.websocket import WebsocketConsumer
class SendConsumer(WebsocketConsumer):
def connect(self):
self.accept()
# # 断开连接
def disconnect(self, close_code):
# Leave room group
pass
# # 接受消息
def receive(self, text_data=None, bytes_data=None):
# 和前端通信接收前端的消息
text_data_json = json.loads(text_data)
message = text_data_json['message']
print(text_data_json, 'websocket-----------')
# 这个代码测试用,给前端发消息,我这里的是测试的demo,前端给我发什么,我回什么
self.send(text_data=json.dumps({
# 'message': json.dumps(msg)
'time': time.time(),
'message': message
}))
- 完成之后,在
routing.py
配置路由
from django.urls import path
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from struction.consumers import SendConsumer
application = ProtocolTypeRouter({
# WebSocket chat handler
"websocket": AuthMiddlewareStack(
URLRouter([
# 这个就是前端与websocket通信时访问的路由
path("ws/channel/websocket/", SendConsumer),
])
),
})
这个位置官方有一个注释,为了区分websocket通信和普通的http通信,最好在路由前面加上一个/ws/
的前缀,这是为了方便在生产环境部署.
特别是对于大型站点,可以配置生产级HTTP服务器(如nginx)以基于以下路径路由请求:
(1)生产级WSGI服务器(如Gunicorn+Django,用于普通HTTP请求),
(2)生产级ASGI服务器(如Daphne+Channels,用于WebSocket请求)。
也就是说我们的Django 项目和 websocket都需要部署.这个后面部署的时候我会再写.
- 以上内容完成之后,我自己写了个简单的测试页面
room.html
的内容如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="100" rows="20"></textarea><br/>
<input id="chat-message-input" type="text" size="100"/><br/>
<input id="chat-message-submit" type="button" value="Send"/>
</body>
<script>
console.log('------')
var chatSocket = new WebSocket(
<!--这个是前端和后端请求连接的地址,这里的ip 端口,换成你自己的即可,这里的端口号,要写你用nginx代理之后的-->
<!--你如果是本地测试,就写你启动项目用的端口就可以,我本地启动的时候用的8008端口 -->
<!-- python manage.py runserver 0.0.0.0:8008 -->
'ws://' + '192.168.1.187:8008' +
'/ws/channel/websocket/');
document.querySelector('#chat-message-submit').onclick = function (e) {
var messageInputDom = document.querySelector('#chat-message-input');
var message = messageInputDom.value;
<!--前端连接之后发送消息 -->
chatSocket.send(JSON.stringify({
'message': message
}));
messageInputDom.value = '';
};
chatSocket.onmessage = function (e) {
console.log(e);
var data = JSON.parse(e.data);
var message = data['message'];
document.querySelector('#chat-log').value += (message + '\n');
console.log(message);
};
chatSocket.onclose = function (e) {
console.error('Chat socket closed unexpectedly');
};
</script>
</html>
- 页面写好之后写一个view视图,测试用
index 函数写在struction.views.py
文件中
urls.py
中要导入indexfrom struction.views import index
- 测试的时候,在浏览器访问
192.168.1.187:8008/websocket/
,效果如下
输入值测试一下效果.
这样websocket在本地就能正常运行了,但是我们的项目开发完之后,还要部署到生产服务器,如果用nginx,uwsgi来部署项目的话,websocket也需要部署才能正常使用,下面我就记录一下我的部署过程.
服务器使用nginx,uwsgi,daphne,supervisor部署项目
- 安装uwsgi
pip install uwsgi
安装完成之后,在项目目录下建立一个uwsgi,放置uwsgi.ini的配置文件,以及日志等信息
-- Video
-- struction
-- uwsgi
-- uwsgi.ini
-- static
-- template
-- Video
...
...
uwsgi.ini配置文件
uwsgi.ini
# 文件里的这个[uwsgi]必须要写,否则会报错的
# Can't find section "uwsgi" in INI configuration file myweb_uwsgi.ini
[uwsgi]
#http =:8090 # 如果不使用nginx 代理,直接运行就配http
socket = 192.168.1.187:8080 # 与nginx 通信的地址和端口,ip就写你自己服务器的地址,port是指定项目运行时的端口号
chdir = /data/videostruction/Video # 项目目录
home = /data/videostruction/venv # 加载虚拟环境的目录
module = Video.wsgi # 改成自己的项目
master = true
workers = 5
processes = 2
threads = 2
vacuum = true
pidfile = %(chdir)/uwsgi/uwsgi.pid
daemonize = %(chdir)/uwsgi/uwsgi.log
#async = 30
ugreen = ''
配置完之后可以先不启动,用supervisor来统一管理.supervisor是一个进程管理工具,可以很方便的监听,启动,停止进程,我们的项目要用到nginx,uwsgi,daphne 都可以用supervisor来管理,方便省事.
- 安装supervisor,部署daphne
(1) 在项目的wsgi.py
文件目录下,添加asgi.py
文件,配置信息如下
asgi.py
import os
import django
#import channels.asgi
from channels.routing import get_default_application
# 这里的Video别忘了改成你自己的项目
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Video.settings")
django.setup()
application = get_default_application()
(2)安装supervisor, apt-get install supervisor
如果有权限要求,在前面加上sudo 即可.安装成功之后,生成supervisor的配置文件,echo_supervisord_conf > /etc/supervisord.conf
,在配置文件中,添加daphne的配置信息.
supervisor.conf
# 我只把需要添加的部分写在这里,原文件的其他配置不需要改动
# 配置daphne有两种方式,我都写在了下面,第一种是官方的写法,第二种是百度到的其他的配置方式
# 两种用一种即可,我把两种都记录下来了
# 第一种
[fcgi-program:asgi]
# nginx 代理时要用到的
socket=tcp://0.0.0.0:8001
directory=/data/videostruction/Video # 这里的目录是你下面执行command命令时所在目录,如果这个地方不对,下面的目录也就执行不了
# 这里注意,如果你的服务器中没有 /run/daphne/的目录,要自己手动建一个,否则会报错,提示找不到目录
command=daphne -u /run/daphne/daphne%(process_num)d.sock --fd 0 --access-log - --proxy-headers Video.asgi:application
numprocs=4
process_name=asgi%(process_num)d
# 自动启动
autostart=true
# 自动重启
autorestart=true
# 这里是日志的输出位置,可以自定义,自定义完要保证目录存在
stdout_logfile=/tmp/asgi.log
redirect_stderr=true
# 第二种
[program:daphne]
socket=tcp://0.0.0.0:8001
directory=/data/videostruction/Video
# 启动时指定ip和端口, Video不要忘记改
command=daphne -b 0.0.0.0 -p 8001 --proxy-headers Video.asgi:application
numprocs=4
process_name=asgi%(process_num)d
autostart=true
autorestart=true
stdout_logfile=/tmp/asgi.log
redirect_stderr=true
[program:uwsgi]
command=uwsgi --ini uwsgi/uwsgi.ini
# 这里的目录含义同上
directory=/data/videostruction/Video
autostart=true
autorestart=true
stdout_logfile=/data/videostruction/Video/uwsgi/uwsgi_out.log
stderr_logfile=/data/videostruction/Video/uwsgi/uwsgi_err.log
修改完配置文件之后需要更新一下
sudo supervisorctl reread
sudo supervisorctl update
(3) 以上内容配置完成之后,来安装nginx,配置nginx 的代理信息,安装nginx的过程我就不写了,我直接写配置过程,安装教程我可以推荐一个.我安装的时候就是参照的这个教程,配置configure时,用的是./configure --prefix=/usr/local/nginx
这种方式.
ngixn教程:https://blog.csdn.net/u012453843/article/details/69396434
这位博主写了很多的博客,都很不错的博客,我还参照过他的博客,搭建了fastdfs服务器,写的也是很详细.
接下来继续nginx的配置,把下面的内容添加到nginx.conf中,
# 这是配置daphne 的,可以单独占用一个端口,也可以和django项目共用一个,我这里是和项目共用一个
upstream wsbackend {
# 这里的端口是你在supervisor中启动daphne时指定的端口
server 192.168.1.187:8001;
}
server {
listen 8090; # 监听的端口(服务器要开放此端口,用户可以通过这个端口访问到项目,如果有请求到这个端口,就转发到项目
server_name localhost;
charset UTF-8;
client_max_body_size 600M; # disable any limits to avoid HTTP 413 for large image uploads
# 转发websocket的请求,请求地址如果以/ws/channel/开头就转发到websocket,
# 这里就是之前官方文档解释的,为了区别普通http请求和ws的请求
location /ws/channel {
proxy_pass http://wsbackend;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location / {
include uwsgi_params;
# 这里的配置信息要和uwsgi.ini的配置信息一致,不然请求可能会转发不过去
uwsgi_pass 192.168.1.187:8080;
uwsgi_read_timeout 20;
}
# 媒体文件的目录
location /media {
alias /data/videostruction/Video/media;
}
# 静态文件的目录
location /static {
#expires 30d;
#autoindex on;
#add_header Cache-Control private;
alias /data/videostruction/Video/static;
}
}
配置完成之后就可以启动supervisor和nginx,nginx的启动也可以用supervisor来管理,我写的时候没有用,如果也想用也可以在supervisor.conf中加上,
[program:nginx]
command=/usr/local/nginx/sbin/nginx
autostart=true
autorestart=true
; 如果/var/log/nginx不存在,要手动创建
stdout_logfile=/var/log/nginx/nginx_out.log
stderr_logfile=/var/log/nginx/nginx_err.log
启动supervisor supervisord -c /etc/supervisord.conf
.
我没有用supervisor管理nginx,还需要单独启动nginx /usr/local/nginx/sbin/nginx
.
如果启动不了,还可以用 /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
.
如果使用nginx代理,测试页面room.html
中的端口和ip不要忘了改.
参考博客: channels实现websocket实时通讯和消息推送https://blog.csdn.net/Wb199812/article/details/100087715
参考博客:django+uwsgi+daphne+supervisor生产环境部署https://www.cnblogs.com/wdliu/p/10032180.html