• websocket/dwebsocket 实现前后端的实时通信


    1.  用bottle框架,自己写一个服务端实现:

    转载   : http://www.linuxyw.com/813.html

    功能:用websocket技术,在运维工具的浏览器上实时显示远程服务器上的日志信息

    一般我们在运维工具部署环境的时候,需要实时展现部署过程中的信息,或者在浏览器中实时显示程序日志给开发人员看。你还在用ajax每隔段时间去获取服务器日志?out了,试试用websocket方式吧

    我用bottle框架,写了个websocket服务端,浏览器连接到websocket server,再用python subprocess获取远程服务器的日志信息,subprocess,就是用Popen调用shell的shell命令而已,这样可以获取到实时的日志了,然后再send到websocket server中,那连接到websocket server的浏览器,就会实时展现出来了

    用二台服务器来实现这个场景,A服务器是websocket服务端,B服务器是日志端

    A服务器是我浏览器本机,websocket服务端也是这台机,IP是:192.168.1.221

    B服务器是要远程查看日志的服务器,我这里用:192.168.1.10

    以下是A服务器的websocket servet的python代码:

     
    #!/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端口

    在电脑桌面,写一个简单的HTML5  javascripts页面,随便命名了,如web_socket.html,这个页面使用了websocket连接到websocket服务端:

     <!DOCTYPE html>
    <html>
    <head>
    </head>
        <style>
            #msg{
                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 
    ' % (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 + '
    ');
                    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  状态释放(有待考证).

  • 相关阅读:
    iOS证书的使用
    ios设备管理
    矩阵的相关问题(旋转矩阵&螺旋矩阵)
    flex实现多列布局效果&对角线布局
    peerdependencies
    数组和对象遍历方法对比
    async和defer
    Promise.all并发限制
    electron+react开发属于自己的桌面应用
    webpack代码切割
  • 原文地址:https://www.cnblogs.com/yitianyouyitian/p/9914056.html
Copyright © 2020-2023  润新知