最近做了一個項目,需要後端主動與前端進行通信,官方推薦使用的是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