Django3+websocket+paramiko實現web頁面實時輸出


一、概述

在上一篇文章中,簡單在瀏覽器測試了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路由)

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),
]

 

設置執行路由對象(指定routing)

最后,將 ASGI_APPLICATION設置為指向路由對象作為根應用程序,修改 settings.py 文件,最后一行添加:
ASGI_APPLICATION = 'django3_websocket.routing.application'

就是這樣!一旦啟用,通道就會將自己集成到Django中,並控制runserver命令。


啟動channel layer

信道層是一種通信系統。它允許多個消費者實例彼此交談,以及與Django的其他部分交談。
通道層提供以下抽象:
通道是一個可以將郵件發送到的郵箱。每個頻道都有一個名稱。任何擁有頻道名稱的人都可以向頻道發送消息。
一組是一組相關的通道。一個組有一個名稱。任何具有組名稱的人都可以按名稱向組添加/刪除頻道,並向組中的所有頻道發送消息。無法枚舉特定組中的通道。
每個使用者實例都有一個自動生成的唯一通道名,因此可以通過通道層進行通信。

這里為了方便部署,直接使用內存作為后備存儲的通道層。有條件的話,可以使用redis存儲。
 

配置CHANNEL_LAYERS

修改 settings.py 最后一行增加配置
CHANNEL_LAYERS = {
     "default": {
         "BACKEND": "channels.layers.InMemoryChannelLayer",
     }
}

 

應用下創建 consumers.py(類似Django視圖)

同步消費者很方便,因為他們可以調用常規的同步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連接")
View Code

注意:修改里面的服務器,用戶名,密碼,腳本名稱。

 

應用下創建 routing.py (類似Django路由)

在web目錄下,創建文件routing.py
添加Channels子路由的配置
from 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>
View Code

 

修改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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM