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{ 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 状态释放(有待考证).