zoukankan      html  css  js  c++  java
  • python2.7实现websocket服务器,可以在web实时显示远程服务器日志

    一、开始的话

      使用python实现websocket服务器,可以在浏览器上实时显示远程服务器的日志。

      之前写了一个发布系统,每次发布版本后,为了了解发布情况(进度、是否有错误)都会登录到服务器上查看日志,有点麻烦,如果发布的服务器比较多,难道要登录到每台服务器去看日志吗?作为新时代的运维,太不能接收这种重复操作的体力劳动了,于是一个看日志的功能就这么诞生了。下面是效果图,页面丑陋不堪,将就着吧。

    二、行动

      打开页面时,自动连接websocket服务器,完成握手,并发送ip和type给服务端,所以可以看不同类型,不同机器上的日志,websocket服务器接收到信息后,去数据库查找对应的日志路径和主机账号密码,然后起一个线程ssh登录到远程服务器上tail查看日志,再推送给浏览器,代码如下:

      1 # coding:utf-8
      2 import os
      3 import struct
      4 import base64
      5 import hashlib
      6 import socket
      7 import threading
      8 import paramiko
      9 
     10 
     11 def get_ssh(ip, user, pwd):
     12     try:
     13         ssh = paramiko.SSHClient()
     14         ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
     15         ssh.connect(ip, 22, user, pwd, timeout=15)
     16         return ssh
     17     except Exception, e:
     18         print e
     19         return "False"
     20 
     21 
     22 def recv_data(conn):    # 服务器解析浏览器发送的信息
     23     try:
     24         all_data = conn.recv(1024)
     25         if not len(all_data):
     26             return False
     27     except:
     28         pass
     29     else:
     30         code_len = ord(all_data[1]) & 127
     31         if code_len == 126:
     32             masks = all_data[4:8]
     33             data = all_data[8:]
     34         elif code_len == 127:
     35             masks = all_data[10:14]
     36             data = all_data[14:]
     37         else:
     38             masks = all_data[2:6]
     39             data = all_data[6:]
     40         raw_str = ""
     41         i = 0
     42         for d in data:
     43             raw_str += chr(ord(d) ^ ord(masks[i % 4]))
     44             i += 1
     45         return raw_str
     46 
     47 
     48 def send_data(conn, data):   # 服务器处理发送给浏览器的信息
     49     if data:
     50         data = str(data)
     51     else:
     52         return False
     53     token = "x81"
     54     length = len(data)
     55     if length < 126:
     56         token += struct.pack("B", length)    # struct为Python中处理二进制数的模块,二进制流为C,或网络流的形式。
     57     elif length <= 0xFFFF:
     58         token += struct.pack("!BH", 126, length)
     59     else:
     60         token += struct.pack("!BQ", 127, length)
     61     data = '%s%s' % (token, data)
     62     conn.send(data)
     63     return True
     64 
     65 
     66 def handshake(conn, address, thread_name):    # 握手建立连接
     67     headers = {}
     68     shake = conn.recv(1024)
     69     if not len(shake):
     70         return False
     71 
     72     print ('%s : Socket start handshaken with %s:%s' % (thread_name, address[0], address[1]))
     73     header, data = shake.split('
    
    ', 1)
     74     for line in header.split('
    ')[1:]:
     75         key, value = line.split(': ', 1)
     76         headers[key] = value
     77 
     78     if 'Sec-WebSocket-Key' not in headers:
     79         print ('%s : This socket is not websocket, client close.' % thread_name)
     80         conn.close()
     81         return False
     82 
     83     MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
     84     HANDSHAKE_STRING = "HTTP/1.1 101 Switching Protocols
    " 
     85                        "Upgrade:websocket
    " 
     86                        "Connection: Upgrade
    " 
     87                        "Sec-WebSocket-Accept: {1}
    " 
     88                        "WebSocket-Origin: {2}
    " 
     89                        "WebSocket-Location: ws://{3}/
    
    "
     90 
     91     sec_key = headers['Sec-WebSocket-Key']
     92     res_key = base64.b64encode(hashlib.sha1(sec_key + MAGIC_STRING).digest())
     93     str_handshake = HANDSHAKE_STRING.replace('{1}', res_key).replace('{2}', headers['Origin']).replace('{3}', headers['Host'])
     94     conn.send(str_handshake)                 # 发送建立连接的信息
     95     print ('%s : Socket handshaken with %s:%s success' % (thread_name, address[0], address[1]))
     96     print 'Start transmitting data...'
     97     print '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -'
     98     return True
     99 
    100 
    101 def dojob(conn, address, thread_name):
    102     handshake(conn, address, thread_name)     # 握手
    103     conn.setblocking(0)                       # 设置socket为非阻塞
    104 
    105     ssh = get_ssh('192.168.1.1', 'root', '123456')   # 连接远程服务器(日志所在的服务器)
    106     ssh_t = ssh.get_transport()
    107     chan = ssh_t.open_session()
    108     chan.setblocking(0)          # 设置非阻塞
    109     chan.exec_command('tail -f /var/log/messages')   # 执行查看日志命令
    110 
    111     while True:            # 下面这个逻辑是在看日志时能停止或继续,写的有点混乱,还没想到优化的方案
    112         clientdata = recv_data(conn)
    113         if clientdata is not None and 'quit' in clientdata:    # 当浏览器点击stop按钮或close按钮时,断开连接
    114             print ('%s : Socket close with %s:%s' % (thread_name, address[0], address[1]))
    115             send_data(conn, 'close connect')
    116             conn.close()
    117             break
    118         while True:
    119             while chan.recv_ready():
    120                 clientdata1 = recv_data(conn)
    121                 if clientdata1 is not None and 'quit' in clientdata1:
    122                     print ('%s : Socket close with %s:%s' % (thread_name, address[0], address[1]))
    123                     send_data(conn, 'close connect')
    124                     conn.close()
    125                     break
    126                 log_msg = chan.recv(10000).strip()    # 接收日志信息
    127                 print log_msg
    128                 send_data(conn, log_msg)
    129             if chan.exit_status_ready():
    130                 break
    131             clientdata2 = recv_data(conn)
    132             if clientdata2 is not None and 'quit' in clientdata2:
    133                 print ('%s : Socket close with %s:%s' % (thread_name, address[0], address[1]))
    134                 send_data(conn, 'close connect')
    135                 conn.close()
    136                 break
    137         break
    138 
    139 
    140 def ws_service():
    141 
    142     index = 1
    143     sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    144     sock.bind(("127.0.0.1", 12345))
    145     sock.listen(100)
    146 
    147     print ('
    
    Websocket server start, wait for connect!')
    148     print '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -'
    149     while True:
    150         connection, address = sock.accept()
    151         thread_name = 'thread_%s' % index
    152         print ('%s : Connection from %s:%s' % (thread_name, address[0], address[1]))
    153         t = threading.Thread(target=dojob, args=(connection, address, thread_name))
    154         t.start()
    155         index += 1
    156 
    157 
    158 ws_service()

    页面代码如下:

    <!DOCTYPE html>
    <html>
    <head>
    <title>WebSocket</title>

    <style>
    #log {
    440px;
    height: 200px;
    border: 1px solid #7F9DB9;
    overflow: auto;
    }
    pre {
    margin: 0 0 0;
    padding: 0;
    border: hidden;
    background-color: #0c0c0c;
    color: #00ff00;
    }
    #btns {
    text-align: right;
    }
    </style>

    <script>
    var socket;
    function init() {
    var host = "ws://127.0.0.1:12345/";

    try {
    socket = new WebSocket(host);
    socket.onopen = function () {
    log('Connected');
    };
    socket.onmessage = function (msg) {
    log(msg.data);
    var obje = document.getElementById("log"); //日志过多时清屏
    var textlength = obje.scrollHeight;
    if (textlength > 10000) {
    obje.innerHTML = '';
    }
    };
    socket.onclose = function () {
    log("Lose Connection!");
    $("#start").attr('disabled', false);
    $("#stop").attr('disabled', true);
    };
    $("#start").attr('disabled', true);
    $("#stop").attr('disabled', false);
    }
    catch (ex) {
    log(ex);
    }
    }
    window.onbeforeunload = function () {
    try {
    socket.send('quit');
    socket.close();
    socket = null;
    }
    catch (ex) {
    log(ex);
    }
    };
    function log(msg) {
    var obje = document.getElementById("log");
    obje.innerHTML += '<pre><code>' + msg + '</code></pre>';
    obje.scrollTop = obje.scrollHeight; //滚动条显示最新数据
    }
    function stop() {
    try {
    log('Close connection!');
    socket.send('quit');
    socket.close();
    socket = null;
    $("#start").attr('disabled', false);
    $("#stop").attr('disabled', true);
    }
    catch (ex) {
    log(ex);
    }
    }
    function closelayer() {
    try {
    log('Close connection!');
    socket.send('quit');
    socket.close();
    socket = null;
    }
    catch (ex) {
    log(ex);
    }
    var index = parent.layer.getFrameIndex(window.name); //先得到当前iframe层的索引
    parent.layer.close(index); //再执行关闭
    }
    </script>

    </head>


    <body onload="init()">
    <div class="row">
    <div class="col-lg-12">
    <div id="log" style=" 100%;height:440px;background-color: #0c0c0c;overflow:scroll;overflow-x: auto;"></div>
    <br>
    </div>
    </div>
    <div class="row">
    <div class="col-lg-12">
    <div id="btns">
    <input disabled="disabled" type="button" class="btn btn-primary btn-sm" value="start" id="start" onclick="init()">
    <input disabled="disabled" type="button" class="btn btn-primary btn-sm" value="stop" id="stop" onclick="stop()" >
    <input type="button" class="btn btn-primary btn-sm" value="close" id="close" onclick="closelayer()" >
    </div>
    </div>
    </div>
    </body>

    </html>
     
  • 相关阅读:
    [WSUS] Windows Server Update Service 更新后,出现错误不能连接
    [SQLServer] 数据库SA用户被锁定或者忘记密码的恢复
    [SQLServer] 内存占用查看
    [IIS] [PHP] 500.19 随机出现
    [OPENSSL下载][证书] OPENSSL将PFX证书转换为PEM格式
    [VS2015] [asp.net] 允许远程访问本机正在DEBUG的服务
    [VS2008] 安装 AnkhSVN 后,选项中选择它,Pending Changes 窗口一闪而过,源代码管理工具自动跳回 【None】
    [IIS] 配置PHP的过程与坑
    10分钟快速入门vue.js
    前端接口请求的几种方式
  • 原文地址:https://www.cnblogs.com/gdou123czh/p/6079672.html
Copyright © 2011-2022 走看看