一、概述
在上一篇文章中,簡單在瀏覽器測試了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 安裝 # ... ]
創建默認路由(主WS路由)
將從一個空路由配置開始。在web目錄下,創建一個文件 routing.py ,內容如下:
from django.urls import re_path,path from . import consumers websocket_urlpatterns = [ # 前端請求websocket連接 path('ws/result/', consumers.SyncConsumer), ]
設置執行路由對象(指定routing)
ASGI_APPLICATION
設置為指向路由對象作為根應用程序,修改 settings.py 文件,最后一行添加:
ASGI_APPLICATION = 'django3_websocket.routing.application'
就是這樣!一旦啟用,通道就會將自己集成到Django中,並控制runserver命令。
啟動channel layer
通道層提供以下抽象:
通道是一個可以將郵件發送到的郵箱。每個頻道都有一個名稱。任何擁有頻道名稱的人都可以向頻道發送消息。
一組是一組相關的通道。一個組有一個名稱。任何具有組名稱的人都可以按名稱向組添加/刪除頻道,並向組中的所有頻道發送消息。無法枚舉特定組中的通道。
每個使用者實例都有一個自動生成的唯一通道名,因此可以通過通道層進行通信。
這里為了方便部署,直接使用內存作為后備存儲的通道層。有條件的話,可以使用redis存儲。
配置CHANNEL_LAYERS
CHANNEL_LAYERS = { "default": { "BACKEND": "channels.layers.InMemoryChannelLayer", } }
應用下創建 consumers.py(類似Django視圖)

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連接")
注意:修改里面的服務器,用戶名,密碼,腳本名稱。
應用下創建 routing.py (類似Django路由)
在web目錄下,創建文件routing.pyfrom django.urls import re_path,path from . import consumers websocket_urlpatterns = [ # 前端請求websocket連接 path('ws/result/', consumers.SyncConsumer), ]
前端頁面連接WebSocket
在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
本文參考鏈接:
https://www.jianshu.com/p/0f75e2623418