python实现websocket服务器,可以在web实时显示远程服务器日志

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python实现websocket服务器,可以在web实时显示远程服务器日志相关的知识,希望对你有一定的参考价值。

一、开始的话

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

  之前做了一个web版的发布系统,但没实现在线看日志,每次发布版本后,都需要登录到服务器上查看日志,非常麻烦,为了偷懒,能在页面点几下按钮完成工作,所以这几天查找了这方面的资料,实现了这个功能,瞬间觉的看日志什么的,太方便了,以后也可以给开发们查日志,再也不用麻烦运维了,废话少说,先看效果吧。

技术分享

 

二、代码

  在实现这功能前,看过别人的代码,发现很多都是只能在web上显示本地的日志,不能看远程主机上的日志,有些能看远程日志的是引用了其他框架(例如bottle,tornado)来实现的,而且所有这些都是重写thread的run方法来实现的,由于本人技术太菜,不知道怎么改成自己需要的样子,而且我是用django这个web框架的,不想引用太多框架,搞的太复杂,所以用python来实现websocket服务器。由于技术问题,代码有点粗糙,不过能实现功能就行,先将就着用吧。

执行下面命令启动django和websocketserver

nohup python manage.py runserver 10.1.12.110 &
nohup python websocketserver.py &

  启动websocket后,接收到请求,起一个线程和客户端握手,然后根据客户端发送的ip和type,去数据库查找对应的日志路径,用paramiko模块ssh登录到远程服务器上tail查看日志,再推送给浏览器,服务端完整代码如下:

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

 

打开页面时,自动连接websocket服务器,完成握手,并发送ip和type给服务端,所以可以看不同类型,不同机器上的日志,

技术分享

 

页面代码如下:

<!DOCTYPE html>  
<html>  
<head>
{% include ‘head_script.html‘ %}
<style>
    #log {
        width: 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/";
            var ips="{{ ip }}";
            var types="{{ type }}";
            var log_msg = ips +":"+ types;

            console.log(log_msg);
            try {
                socket = new WebSocket(host);  
                socket.onopen = function () {
                    log(Connected);
                    socket.send(log_msg);
                };  
                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="width: 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>  
{% include ‘foot_script.html‘ %}
</html> 

 

 

 

以上是关于python实现websocket服务器,可以在web实时显示远程服务器日志的主要内容,如果未能解决你的问题,请参考以下文章

Python 基于websocket实时通信的实现—GoEasy

python 实现websocket

python测试开发django-81.dwebsocket实现websocket

websocket面试题

python---websocket的使用

python---websocket