websocket/dwebsocket 實現先後端的實時通訊

1.  用bottle框架,本身寫一個服務端實現:

轉載   : http://www.linuxyw.com/813.htmljavascript

 

功能:用websocket技術,在運維工具的瀏覽器上實時顯示遠程服務器上的日誌信息html

通常咱們在運維工具部署環境的時候,須要實時展示部署過程當中的信息,或者在瀏覽器中實時顯示程序日誌給開發人員看。你還在用ajax每隔段時間去獲取服務器日誌?out了,試試用websocket方式吧html5

我用bottle框架,寫了個websocket服務端,瀏覽器鏈接到websocket server,再用python subprocess獲取遠程服務器的日誌信息,subprocess,就是用Popen調用shell的shell命令而已,這樣能夠獲取到實時的日誌了,而後再send到websocket server中,那鏈接到websocket server的瀏覽器,就會實時展示出來了java

用二臺服務器來實現這個場景,A服務器是websocket服務端,B服務器是日誌端python

A服務器是我瀏覽器本機,websocket服務端也是這臺機,IP是:192.168.1.221jquery

B服務器是要遠程查看日誌的服務器,我這裏用:192.168.1.10linux

如下是A服務器的websocket servet的python代碼:nginx

 
#!/usr/bin/env python
#coding=utf-8
# __author__ = '戴儒鋒'
# http://www.linuxyw.com
"""
    執行代碼前須要安裝
    pip install bottle
    pip install websocket-client
    pip install bottle-websocket
"""
from bottle import get, run
from bottle.ext.websocket import GeventWebSocketServer
from bottle.ext.websocket import websocket
users = set()   # 鏈接進來的websocket客戶端集合
@get('/websocket/', apply=[websocket])
def chat(ws):
    users.add(ws)
    while True:
        msg = ws.receive()  # 接客戶端的消息
        if msg:
            for u in users:
                u.send(msg) # 發送信息給全部的客戶端
        else:
            break
    # 若是有客戶端斷開鏈接,則踢出users集合
    users.remove(ws)
run(host='0.0.0.0', port=8000, server=GeventWebSocketServer)

 

 

記得安裝bottle、websocket-client 、bottle-websocket 模塊,服務端容許全部的IP訪問其8000端口web

 

在電腦桌面,寫一個簡單的HTML5  javascripts頁面,隨便命名了,如web_socket.html,這個頁面使用了websocket鏈接到websocket服務端:ajax

 <!DOCTYPE html>
<html>
<head>
</head>
    <style>
        #msg{
            width:400px; height:400px; overflow:auto; border:2px solid #000000;background-color:#000000;color:#ffffff;
    }
    </style>
</head>
<body>
    <p>實時日誌</p>
    <div id="msg"></div>
    <script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
    <script>
    $(document).ready(function() {
        /* !window.WebSocket、window.MozWebSocket檢測瀏覽器對websocket的支持*/
        if (!window.WebSocket) {
            if (window.MozWebSocket) {
                window.WebSocket = window.MozWebSocket;
            } else {
                $('#msg').prepend("<p>你的瀏覽器不支持websocket</p>");
            }
        }
        /* ws = new WebSocket 建立WebSocket的實例  注意設置對如下的websocket的地址哦*/
        ws = new WebSocket('ws://192.168.1.221:8000/websocket/');
        /*
            ws.onopen  握手完成並建立TCP/IP通道,當瀏覽器和WebSocketServer鏈接成功後,會觸發onopen消息
            ws.onmessage 接收到WebSocketServer發送過來的數據時,就會觸發onmessage消息,參數evt中包含server傳輸過來的數據;
        */
        ws.onopen = function(evt) {
            $('#msg').append('<li>websocket鏈接成功</li>');
        }
        ws.onmessage = function(evt) {
            $('#msg').prepend('<li>' + evt.data + '</li>');
        }
    });
</script>
</body>
</html>

 

 

注意:WebSocket('ws://192.168.1.221:8000/websocket/');  這裏的192.168.1.221必定要改爲你的websocket服務端IP,切記!!!

到這裏,就搞定瀏覽器鏈接到websocket服務端的場景了,如今要A服務器裏寫一段代碼,去採集B服務器的實時信息了,其實採集原理很簡單,就是使用shell中的tailf命令,實時顯示最新的信息而已,咱們在這段腳本中,使用subprocess.Popen()來遠程查看日誌信息:

python代碼以下:

 

#!/usr/bin/python
# encoding=utf-8
import subprocess
import time
from websocket import create_connection
# 配置遠程服務器的IP,賬號,密碼,端口等,因我作了雙機密鑰信任,因此不須要密碼
r_user = "root"
r_ip = "192.168.1.10"
r_port = 22
r_log = "/tmp/web_socket.log"   # 遠程服務器要被採集的日誌路徑
# websocket服務端地址
ws_server = "ws://192.168.1.221:8000/websocket/"
# 執行的shell命令(使用ssh遠程執行)
cmd = "/usr/bin/ssh -p {port} {user}@{ip} /usr/bin/tailf {log_path}".format(user=r_user,ip=r_ip,port=r_port,log_path=r_log)
def tailfLog():
    """獲取遠程服務器實時日誌,併發送到websocket服務端"""
    popen = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
    print('鏈接成功')
    ws = create_connection(ws_server)   # 建立websocket鏈接
    while True:
        line = popen.stdout.readline().strip()  #獲取內容
        if line:
            ws.send(line)   #把內容發送到websocket服務端
        print time.time()
if __name__ == '__main__':
    tailfLog()

 

文章最後再解析subprocess.Popen的原理和功能

執行websocket服務端腳本和上面這個websocket客戶端採集腳本,再打開用瀏覽器打開上面的html5頁面後,環境就基本部署好了,雙websocket客戶端鏈接到websocket服務端中

上面腳本指定的r_log = "/tmp/web_socket.log"日誌路徑,咱們須要生成這個日誌文件,並不停地往裏面寫入日誌,這樣才能在瀏覽器中實時顯示效果(真實場景中,能夠指定服務器某日誌,如apache,nginx日誌等)

咱們在B服務器寫一段python代碼,而後每隔一秒就往r_log = "/tmp/web_socket.log"日誌中寫入內容:

python代碼以下:

#!/usr/bin/env python
#coding=utf-8
import time
import random
log_path = '/tmp/web_socket.log'
while 1:
    with open(log_path,'a') as f:
        f.write('[%s]   %s \n' % (time.ctime(),random.random()))
    time.sleep(1)

 

2.  使用dwebsocket

實驗環境: 

django 2.1

python 3.5

ansible 2.7

dwebsocket 0.5.5

 

參考:   https://www.cnblogs.com/huguodong/p/6611602.html 

 

安裝:

pip install  dwebsocket

 

一些方法和屬性:

1.request.is_websocket()

若是是個websocket請求返回True,若是是個普通的http請求返回False,能夠用這個方法區分它們。

2.request.websocket

在一個websocket請求創建以後,這個請求將會有一個websocket屬性,用來給客戶端提供一個簡單的api通信,若是request.is_websocket()是False,這個屬性將是None。

3.WebSocket.wait()

返回一個客戶端發送的信息,在客戶端關閉鏈接以前他不會返回任何值,這種狀況下,方法將返回None

4.WebSocket.read()

 若是沒有從客戶端接收到新的消息,read方法會返回一個新的消息,若是沒有,就不返回。這是一個替代wait的非阻塞方法

5.WebSocket.count_messages()

 返回消息隊列數量

6.WebSocket.has_messages()

 若是有新消息返回True,不然返回False

7.WebSocket.send(message)

 向客戶端發送消息

8.WebSocket.__iter__()

 websocket迭代器

 

 

客戶端:

{% extends 'base.html' %}
{% load widget_tweaks %}
{% load staticfiles %}

{% block title %}
<title>遊戲腳本上傳</title>
{% endblock %}

{% block content_header %}
遊戲腳本上傳
{% endblock %}

{% block morejs %}
<script>
    $(function() {
        $('#id_log_start').click(function () {
            if (window.s) {
                window.s.close()
            }
            /*建立socket鏈接*/
            var socket = new WebSocket("ws://" + window.location.host + "/ansible/websocket/");
            socket.onopen = function () {
                console.log('WebSocket open');//成功鏈接上Websocket
            };
            socket.onmessage = function (e) {
                console.log('message: ' + e.data);//打印出服務端返回過來的數據
                $('#id_log').append( e.data + '\n');
                document.getElementById('id_log').scrollTop = document.getElementById('id_log').scrollHeight  //testarea自動向下滾動
            };
            // Call onopen directly if socket is already open
            if (socket.readyState == WebSocket.OPEN) socket.onopen();
            window.s = socket;
        });

        $('#id_log_stop').click(function () {
            if (window.s) {
                window.s.close();//關閉websocket
                console.log('websocket closed');
            }
        });

    });

</script>
{% endblock %}

{% block onload %}
onload="business_batch_update_dis()"
{% endblock %}


{% block content %}
<section class="content">
  <div class="row">
    <div class="col-xs-12">
      <div class="box">
          <div class="box-header">
            <div class="row">
               <form action="" method="post" class="form-horizontal">
                {% csrf_token %}
                    <div>
                        <div class="col-md-2">
                            <select class="form-control" name="name_game" id="id_game" onchange="getcenter('{% url 'business:getcenter' %}','{{csrf_token}}')" >
                                <option value="" selected="selected">--選擇遊戲--</option>
                                {% for i in game %}
                                <option value="{{i.id}}">{{i}}</option>
                                {% endfor %}
                            </select>
                        </div>
                    </div>

                    <div>
                        <div class="col-md-2">
                            <select class="form-control" name="name_center" id="id_center" onchange="getarea('{% url 'business:getarea' %}','{{csrf_token}}')" >
                                <option value="" selected="selected">--選擇中心--</option>
                            </select>
                        </div>
                    </div>

                    <div>
                        <div class="col-md-2">
                            <select class="form-control"  name="name_area" id="id_area" onchange="getserver('{% url 'business:getserver' %}','{{csrf_token}}')" >
                                <option value="" selected="selected">--選擇大區--</option>
                            </select>
                        </div>
                    </div>

                </form>
            </div>
            <br/>
            <br/>

            <div class="row">
              <div class="col-md-2">
                  <form class="form-horizontal" id="id_batch_update" method="post" action="" onsubmit="return update_version_check()">
                        {% csrf_token %}
                        <div >
                            <select class="form-control" size="27" id="id_server" name="name_server" multiple="multiple" >
                        </select>

                        </div>
                    </form>
                </div>

                <div class="col-md-10">

                    <div class="row">
                        <label class="col-md-2 " >參數配置:</label>
                    </div>

                    <div class="row form-horizontal">
                        <div class="col-md-3 ">
                            <input name="name_script" type="text" class="form-control" placeholder="src:源文件" required="required">
                        </div>

                        <div class="col-md-3">
                            <input name="name_path" type="text" class="form-control" placeholder="dest:目標路徑" required="required">
                        </div>

                        <div class="col-md-2">
                            <button class="btn btn-success" type="button" id="id_submit_upload" onclick="script_upload_check('{{csrf_token}}')">上傳</button>
                        </div>

                        <div class="col-md-2">
                            <button class="btn btn-success" type="button" id="id_log_start" >查看日誌</button>
                        </div>

                        <div class="col-md-2">
                            <button class="btn btn-success" type="button" id="id_log_stop" >中止查看</button>
                        </div>

                    </div>


                    <br/>
                    <br/>

                    <div class="row">
                        <label class="col-md-2 " >執行結果:</label>
                    </div>

                    <div class="row">
                        <div class="col-md-12">

                            <textarea name="content" id="id_result" rows="19" cols="60" class="form-control">
                            </textarea>
                        </div>
                    </div>
                    <br/>
                    <br/>

                    <div class="row">
                        <label class="col-md-2 " >Ansible日誌:</label>
                    </div>

                    <div class="row">
                        <div class="col-md-12">
                            <textarea name="content" id="id_log" rows="19" cols="60" class="form-control">
                            </textarea>
                        </div>
                    </div>
                </div>



                </div>

                </div>

            </div>



          </div>

        <div class="box-body">
          <div class="dataTables_wrapper form-inline dt-bootstrap" id="example2_wrapper">

          </div>
        </div>
        <!-- /.box-body --></div>
      <!-- /.box -->
      <!-- /.box --></div>
    <!-- /.col --></div>
  <!-- /.row --></section>
{% endblock %}

 

app 的views

from dwebsocket import  accept_websocket
import subprocess

@accept_websocket
def an_websocket(request):
    if request.is_websocket():
        print('ssssssssssssssssssssssssss')
        print ('socket..............')
        r_log = "/tmp/ansible.log"  # 遠程服務器要被採集的日誌路徑
        # 執行的shell命令(使用ssh遠程執行)
        cmd = "/usr/bin/tail -f {log_path}".format(log_path=r_log)
        popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        while True:
            line = popen.stdout.readline().strip()  #獲取內容
            print (line)
            if line:
                # print('進入發送流程.........')
                # for i in line:
                request.websocket.send(line)

            print (time.time())
        # request.websocket.send('a')

    else:
        return HttpResponse('點個贊')

 

app 的urls.py

 url(r'^websocket/$', an_websocket, name="an_websocket"),u

用的是namespaace+name的方式.

 

出現的問題:

1. 上面查看日誌用的是  tail -f 命令。  在 客戶端關閉後發現 socket 鏈接是  CLOSE_WAIT  狀態。而且 tail -f 的任務也沒有結束。估計的等待 CLOSE_WAIT  狀態釋放(有待考證).

相關文章
相關標籤/搜索