前言:因为项目中需要服务器实时将结果反馈给前端,之前采用的ajax轮询的方式,缺点大家都懂的。因为久仰websocket大名,正好最近有些时间,就想把原有项目升级一下,由ajax升级到websocket,毕竟websocket可以由服务器主动发送数据,优势太明显了。
这篇博文分为两个阶段,即开发配置与部署。其实开发(demo)只用了一会儿就完成了,并且在python manage.py runserver 下前后台测试成功,以为很快可以发布了。结果部署到生产环境中居然费了两天时间。因为之前没用过,所以费了好大的力气,特别是开始想用IIS的URL重写功能,结果研究了一天也没成功(本来用IIS+Django的案例就很少,加上channels就更少了,我可能还是哪里没设置好,如果有谁配置成功了希望能教教我),后来改用nginx也磨了大半天才成功。这里记录一下流程的步骤,以免将来遗忘。
各种版本:python--3.8.3? django--3.1.4? nginx--1.21.4? chanels--3.0.4??OS:window2012Server?
一,开发配置
1,安装channels:
? ? ? ? 因为新版django中已经淘汰了传统的websocket,将功能融入到了更全面的channels中,所以需要先在命令行安装channels
pip install channels
2,配置项目setting.py,首先注册一下channels
INSTALLED_APPS = [
...
'channels',
...
]
? ? ? ? 再在原来指定WSGI的后面添加ASGI变量
WSGI_APPLICATION = 'heidanew.wsgi.application' #原项目中有的
ASGI_APPLICATION = 'heidanew.asgi.application' #新添加的,就是将wsgi都改成asgi
这里解释一下这两个名词:WSGI 是基于HTTP 协议模式的,不支持WebSocket ,而ASGI 的诞生则是为了解决Python 常用的WSGI 不支持当前Web 开发中的一些新的协议标准。同时,ASGI 对于WSGI 原有的模式的支持和WebSocket 的扩展,即ASGI 是WSGI 的扩展。
3,修改asgi.py中的内容(django3以上应该都有这个文件,如果没有可以创建一个,与setting.py平级)如下:
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from . import routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'heidanew.settings')
application = ProtocolTypeRouter({
"http":get_asgi_application(),
"websocket": URLRouter(routing.websocket_urlpatterns)
})
因为默认的asgi中没有websocket协议,所以利用这个入口文件将websocket加进来,注意上面有一个from . import routing,现在还没有这个文件,所以按这个顺序走的时候那里会报错,一会儿我们就创建这个文件
4,与asgi.py平级的目录中新建routing.py文件,
我的代码如下:
from django.urls import re_path
from ws_app import consumers
websocket_urlpatterns=[
re_path(r'ws/channels/',consumers.ChatConsumer.as_asgi())
]
routing.py相当于wsgi使用的urls.py,都是提供路由作用的。我这里只写了一个路径,你当然可以写多个路径。上面引入 的ws_app中的consumners,是下面即将要创建的app及其中的一个模块。
5, 用startapp命令创建一个ws_app的app,创建consumers.py文件(app名和文件名当然可以随意起名,但要和第4步中的引入对应上)
?consumers.py中的代码如下:
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
class ChatConsumer(WebsocketConsumer):
def websocket_connect(self, message):
#有客户端向后端改善websocket请求时自动触发
#服务端允许执行下行代码,如果不允许可以用 raise StopConsumner()拒绝客户端的连接请求
self.accept()
def websocket_receive(self, message):
#客户端发来数据时触发,message是客户端发来的数据(一个字典)
print(message)
self.send("服务器端的内容")#向客户端发送数据
def websocket_disconnect(self, message):
# 断开连接时触发
raise StopConsumer()
这里的三个“回调”函数分别分别连接成功时,接收客户端数据时,连接关闭时,函数名不能变,我这里每当接收到客户端请求时就自动发一个“服务端的内容”给客户端,而且你也可以在程序中任意位置使用self.send随时将数据主动发送给客户端。
二.,前端代码?
<body>
<button onclick="sendData(123)">发送</button>
</body>
<script type="text/javascript">
if ("WebSocket" in window) {
// 打开一个 web socket
ws = new WebSocket("ws://127.0.0.1:7002/ws/channels/");
// 连接建立后的回调函数
ws.onopen = function() {
// Web Socket 已连接上,使用 send() 方法发送数据
ws.send("admin:123456");
console.log("正在发送:admin:123456");
};
// 接收到服务器消息后的回调函数
ws.onmessage = function(evt) {
var received_msg = evt.data;
if (received_msg.indexOf("sorry") == -1) {
console.log("收到消息:" + received_msg);
}
};
// 连接关闭后的回调函数
ws.onclose = function() {
// 关闭 websocket
console.log("连接已关闭...");
};
} else {
// 浏览器不支持 WebSocket
console.log("您的浏览器不支持 WebSocket!");
}
// setTimeout(() => {
// ws.send("aaabbccc");
// console.log("正在发送:aabccc");
// }, 1000)
function sendData(str) {
ws.send(str)
}
</script>
现在,可以使用python manage.py runserver 7002开启测试服务器,打开前端网页时直接可以看到下图所示结果,就表示成功了
三,部署nginx服务器?
你觉得这样就够了?远远不行,因为思路为外网进来的时候是不可以使用这个测试(开发环境)w作为服务器的,需要绑定域名和使用daphne服务器进行托管asgi服务。思路如下:
进站域名如果是ws协议开头的,如ws://域名:7002/ws/channels/ 则转换为ws://127.0.0.1:7002/ws/channels。此时的127.0.0.1:7002并不是nginx服务器,而是为ASGI提供服务的daphne程序。具体步骤如下:(以下操作均在远程服务器进行)
1,启动daphne服务:
在终端运行执行以下命令,启动127.0.0.1:7002的ASGI服务
daphne -p 7002 heidanew.asgi:application
启动后如下所示:
这里注意一下:这个daphne在官网上看是在unix环境下 运行的,但实际上我的操作系统是window也可以运行
2,nginx中的nginx.conf文件内容,其中的server配置这么写:
server {
listen 7002;
server_name 你的域名;
location / {#一般的http协议
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Nginx-Proxy true;
proxy_set_header Connection "";
proxy_pass http://域名:7001;
proxy_redirect default;
#root html;
}
location /ws{#ws协议时
proxy_http_version 1.1;
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-Nginx-Proxy true;
proxy_redirect off;
client_max_body_size 10m;
proxy_pass http://127.0.0.1:7002;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_connect_timeout 300s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
说明一下,上面的代码将访问网站的请求分为2种,http(https)或ws(wss),前者直接走前面的请求,实际上我还有一个IIS服务器,是指向 “域名:7001"地址的,所以正常的请求就直接走7001这个地址即可即proxy_pass http://域名:7001,但如果是ws协议,则走的proxy_pass http://127.0.0.1:7002;运行的是本机的daphne服务。、
我在配置IIS时也想进行这种URL重写,但是不知为什么一直没有成功。以上就是配置的全部内容,现在在远程测试一下。将前端代码中的websocket对象在定义时改成你的域名,即:
ws = new WebSocket("ws://域名:7002/ws/channels/");
然后在本地运行(即访问远程服务器)应该可以得到下图的结果了。
|