在上一篇文章中,简单在浏览器测试了websocket,链接如下:https://www.cnblogs.com/xiao987334176/p/13615170.html
但是,我们最终的效果是web页面上,能够实时输出结果,比如执行一个shell脚本。
以母鸡下蛋的例子,来演示一下,先来看效果:
操作系统:windows 10
python版本:3.7.9
操作系统:centos 7.6
ip地址:192.168.31.196
说明:windows10用来运行django项目,centos系统用来执行shell脚本。脚本路径为:/opt/test.sh,内容如下:
#!/bin/bash
for i in {1..10}
do
sleep 0.1
echo 母鸡生了$i个鸡蛋;
done
新建项目:django3_websocket,应用名称:web
安装paramiko模块
pip3 install paramiko
编辑 settings.py
将Channels库添加到已安装的应用程序列表中。编辑 settings.py 文件,并将channels
添加到INSTALLED_APPS
设置中。
INSTALLED_APPS = [
# ... 'channels', # 【channels】(第1步)pip install -U channels 安装
# ...
]
Channels路由配置类似于Django URLconf,因为当通道服务器接收到HTTP请求时,它告诉通道运行什么代码。 将从一个空路由配置开始。在web目录下,创建一个文件 routing.py ,内容如下:
from django.urls import re_path,path
from . import consumers
websocket_urlpatterns = [
# 前端请求websocket连接
path('ws/result/', consumers.SyncConsumer),
]
最后,将ASGI_APPLICATION
设置为指向路由对象作为根应用程序,修改 settings.py 文件,最后一行添加:
ASGI_APPLICATION = 'django3_websocket.routing.application'
就是这样!一旦启用,通道就会将自己集成到Django中,并控制runserver命令。
信道层是一种通信系统。它允许多个消费者实例彼此交谈,以及与Django的其他部分交谈。 通道层提供以下抽象: 通道是一个可以将邮件发送到的邮箱。每个频道都有一个名称。任何拥有频道名称的人都可以向频道发送消息。 一组是一组相关的通道。一个组有一个名称。任何具有组名称的人都可以按名称向组添加/删除频道,并向组中的所有频道发送消息。无法枚举特定组中的通道。 每个使用者实例都有一个自动生成的唯一通道名,因此可以通过通道层进行通信。
这里为了方便部署,直接使用内存作为后备存储的通道层。有条件的话,可以使用redis存储。
修改 settings.py 最后一行增加配置
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer",
}
}
同步消费者很方便,因为他们可以调用常规的同步I / O函数,例如那些在不编写特殊代码的情况下访问Django模型的函数。 但是,异步使用者可以提供更高级别的性能,因为他们在处理请求时不需要创建其他线程。
这里使用同步消费,因为我测试异步消费时,web页面并不能实时展示结果。只能使用同步模式才行。
在web目录下,创建文件consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
import paramiko
from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer
from asgiref.sync import async_to_sync
# 同步方式,仅作示例,不使用
class SyncConsumer(WebsocketConsumer):
def connect(self):
self.username = "xiao" # 临时固定用户名
print('WebSocket建立连接:', self.username)
# 直接从用户指定的通道名称构造通道组名称
self.channel_group_name = 'msg_%s' % self.username
# 加入通道层
# async_to_sync(…)包装器是必需的,因为ChatConsumer是同步WebsocketConsumer,但它调用的是异步通道层方法。(所有通道层方法都是异步的。)
async_to_sync(self.channel_layer.group_add)(
self.channel_group_name,
self.channel_name
)
# 接受WebSocket连接。
self.accept()
async_to_sync(self.channel_layer.group_send)(
self.channel_group_name,
{
'type': 'get_message',
}
)
def disconnect(self, close_code):
print('WebSocket关闭连接')
# 离开通道
async_to_sync(self.channel_layer.group_discard)(
self.channel_group_name,
self.channel_name
)
# 从WebSocket中接收消息
def receive(self, text_data=None, bytes_data=None):
print('WebSocket接收消息:', text_data,type(text_data))
text_data_json = json.loads(text_data)
message = text_data_json['message']
# print("receive message",message,type(message))
# 发送消息到通道
async_to_sync(self.channel_layer.group_send)(
self.channel_group_name,
{
'type': 'get_message',
'message': message
}
)
# 从通道中接收消息
def get_message(self, event):
# print("event",event,type(event))
if event.get('message'):
message = event['message']
# 判断消息
if message == "close":
# 关闭websocket连接
self.disconnect(self.channel_group_name)
print("前端关闭websocket连接")
# 判断消息,执行脚本
if message == "laying_eggs":
# 执行的命令或者脚本
command = 'bash /opt/test.sh'
# 远程连接服务器
hostname = '192.168.31.196'
username = 'root'
password = 'root'
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=hostname, username=username, password=password)
# 务必要加上get_pty=True,否则执行命令会没有权限
stdin, stdout, stderr = ssh.exec_command(command, get_pty=True)
# result = stdout.read()
# 循环发送消息给前端页面
while True:
nextline = stdout.readline().strip() # 读取脚本输出内容
# print(nextline.strip())
# 发送消息到客户端
self.send(
text_data=nextline
)
print("已发送消息:%s" % nextline)
# 判断消息为空时,退出循环
if not nextline:
break
ssh.close() # 关闭ssh连接
# 关闭websocket连接
self.disconnect(self.channel_group_name)
print("后端关闭websocket连接")
注意:修改里面的服务器,用户名,密码,脚本名称。
在web目录下,创建文件routing.py
添加Channels子路由的配置
from django.urls import re_path,path
from . import consumers
websocket_urlpatterns = [
# 前端请求websocket连接
path('ws/result/', consumers.SyncConsumer),
]
在templates目录下,新建文件index.html,内容如下:
<!DOCTYPE html >
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>测试demo</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
</head>
<body>
<div class="container">
<div style="height: 30px"></div>
<button type="button" id="execute_script" class="btn btn-success">查看日志</button>
<h4>日志内容:</h4>
<div style="height: 600px;overflow: auto;" id="content_logs">
<div id="messagecontainer" style="font-size: 16px;background-color: black;color: white">
</div>
</div>
</div>
</body>
<script type="text/javascript">
// 点击按钮
$('#execute_script').click(function () {
// 新建websocket连接
const chatSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/result/'
);
// 连接建立成功事件
chatSocket.onopen = function () {
console.log('WebSocket open');
//发送字符: laying_eggs到服务端
chatSocket.send(JSON.stringify({
'message': 'laying_eggs'
}));
console.log("发送完字符串laying_eggs");
};
// 接收消息事件
chatSocket.onmessage = function (e) {
{#if (e.data.length > 0) {#}
//打印服务端返回的数据
console.log('message: ' + e.data);
// 转换为字符串,防止卡死testestt
$('#messagecontainer').append(String(e.data) + '<br/>');
//滚动条自动到最底部
$("#content_logs").scrollTop($("#content_logs")[0].scrollHeight);
{# }#}
};
// 关闭连接事件
chatSocket.onclose = function (e) {
console.log("connection closed (" + e.code + ")");
chatSocket.send(JSON.stringify({
'message': 'close'
}));
}
});
</script>
</html>
修改urls.py,增加首页
from django.contrib import admin
from django.urls import path
from web import views
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index),
]
修改web目录下的views.py,内容如下:
from django.shortcuts import render
# Create your views here.
def index(request):
return render(request,'index.html')
使用Pycharm直接启动项目,或者使用命令行启动
python manage.py runserver
http://127.0.0.1:8000/index/
点击查看日志,效果就是文章开头部分的动态效果了。
完整代码在github中,地址:
https://github.com/py3study/django3_websocket
本文参考链接: