WebSocket 协议主要用于解决Web前端与后台数据交互问题,在WebSocket技术没有被定义之前,前台与后端通信需要使用轮询的方式实现,WebSocket则是通过握手机制让客户端与服务端建立全双工通信,从而实现了更多复杂的业务需求。

在各种复杂的Web框架中往往集成有自己的WebSocket插件,而这里面隐藏了许多实现细节,下面我们将自己实现一个纯Python版的WebSocket通信功能,并用该技术实现动态绘图,远程CMD执行工具等。

前端index.html代码如下.

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<script src="https://cdn.lyshark.com/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<ul id="content"></ul>
<form class="form">
<input type="text" placeholder="请输入发送的消息" class="message" id="message"/>
<input type="button" value="连接" id="connect" class="connect"/>
<input type="button" value="发送" id="send" class="connect"/>
</form> <script type="text/javascript">
var oUl=document.getElementById('content');
var oConnect=document.getElementById('connect');
var oSend=document.getElementById('send');
var websocket=null;
oConnect.onclick=function(){
websocket=new WebSocket('ws://127.0.0.1:10083'); <!--客户端链接后触发-->
websocket.onopen=function(){
oUl.innerHTML+="<li>客户端已连接</li>";
}
<!--收到消息后触发-->
websocket.onmessage=function(evt){
oUl.innerHTML+="<li>"+evt.data+"</li>";
}
<!--关闭后触发-->
websocket.onclose=function(){
oUl.innerHTML+="<li>客户端已断开连接</li>";
};
<!--出错后触发-->
websocket.onerror=function(evt){
oUl.innerHTML+="<li>"+evt.data+"</li>";
};
};
oSend.onclick=function(){
if(websocket){
websocket.send($("#message").val())
}
}
</script>
</body>
</html>

后端的main.py执行处理任务,主要处理流程集中在handler_msg函数上.

import socket,struct,hashlib,base64
import threading # 获取请求头部数据,并将请求头转换为字典
def get_headers(data):
headers = {}
data = str(data, encoding="utf-8")
header, body = data.split("\r\n\r\n", 1)
header_list = header.split("\r\n")
for i in header_list:
i_list = i.split(":", 1)
if len(i_list) >= 2:
headers[i_list[0]] = "".join(i_list[1::]).strip()
else:
i_list = i.split(" ", 1)
if i_list and len(i_list) == 2:
headers["method"] = i_list[0]
headers["protocol"] = i_list[1]
print("请求类型: {} 请求协议: {}".format(i_list[0],i_list[1]))
return headers # 接收数据时的解码过程
def parse_payload(payload):
payload_len = payload[1] & 127
if payload_len == 126:
mask = payload[4:8]
decoded = payload[8:]
elif payload_len == 127:
mask = payload[10:14]
decoded = payload[14:]
else:
mask = payload[2:6]
decoded = payload[6:] # 将所有数据全部收集起来,对所有字符串编码
bytes_list = bytearray()
for i in range(len(decoded)):
chunk = decoded[i] ^ mask[i % 4]
bytes_list.append(chunk)
body = str(bytes_list, encoding='utf-8')
return body # 封装并发送数据到浏览器
def send_msg(conn, msg_bytes):
# 接收的第一个字节都是x81不变
first_byte = b"\x81"
length = len(msg_bytes)
if length < 126:
first_byte += struct.pack("B", length)
elif length <= 0xFFFF:
first_byte += struct.pack("!BH", 126, length)
else:
first_byte += struct.pack("!BQ", 127, length)
msg = first_byte + msg_bytes
conn.sendall(msg)
return True # 从浏览器中接收数据
def recv_msg(conn):
data_recv = conn.recv(8096)
if data_recv[0:1] == b"\x81":
data_parse = parse_payload(data_recv)
return data_parse
return False # 建立握手流程并创建 handler_msg 完成数据收发
def handler_accept(sock):
while True:
conn, addr = sock.accept()
data = conn.recv(8096)
headers = get_headers(data) # 对请求头中的sec-websocket-key进行加密
response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
"Upgrade:websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: %s\r\n" \
"WebSocket-Location: ws://%s\r\n\r\n"
# 加盐操作,此处是H5规范定义好的
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
if headers.get('Sec-WebSocket-Key'):
value = headers['Sec-WebSocket-Key'] + magic_string
# 对数据进行加解密
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'), headers.get("Host"))
# 相应握手包数据
conn.sendall(bytes(response_str, encoding="utf-8"))
t = threading.Thread(target=handler_msg, args=(conn, ))
t.start() # 主函数,用于实现数据交互
def handler_msg(connect):
with connect as connect_ptr:
while True:
try:
recv = recv_msg(connect_ptr)
print("接收数据: {}".format(recv))
send_msg(connect_ptr, bytes("hello lyshark", encoding="utf-8"))
except Exception:
exit(0) if __name__ == "__main__":
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 10083))
sock.listen(5)
t = threading.Thread(target=handler_accept(sock))
t.start()

上方代码只是一个案例,让我们继续改进,增加一个动态图形绘制功能。

前端代码需要配合echarts绘图库代码如下:

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<script type="text/javascript" src="https://cdn.lyshark.com/echarts/5.0.0/echarts.min.js"></script>
</head>
<body>
<center><div id="main" style="height:400px;width:90%;border:1px solid #eecc11;padding:10px;"></div></center>
<script type="text/javascript" charset="UTF-8">
var display = function(time,cpu) {
var main = echarts.init(document.getElementById(("main")));
var option = {
xAxis: {
type: 'category',
data: time
},
yAxis: {
type: 'value'
},
series: [{
type: 'line',
smooth:0.3,
symbol: 'none',
color: 'blue',
smooth: true,
areaStyle: {
color: '#0000CD',
origin: 'start',
opacity: 0.5
},
data: cpu
}]
};
main.setOption(option,true);
};
</script>
<script type="text/javascript" charset="UTF-8">
var ws=new WebSocket('ws://127.0.0.1:10083');
var time =["","","","","","","","","",""];
var cpu = [0,0,0,0,0,0,0,0,0,0]; ws.onmessage=function(evt)
{
var recv = JSON.parse(evt.data);
time.push(recv.response[0]);
cpu.push(parseFloat(recv.response[1]));
if(time.length >=10){
time.shift();
cpu.shift();
console.log("时间:" + time + " --> CPU数据: " + cpu);
display(time,cpu)
}
}
</script>
</body>
</html>

后台我们主要代码不需要动,只需要修改handler_msg处理流程即可.

# 主函数,用于实现数据交互
def handler_msg(conn):
with conn as c:
while True:
try:
times = time.strftime("%M:%S", time.localtime())
data = psutil.cpu_percent(interval=None, percpu=True)
print("处理时间: {} --> 处理负载: {}".format(times, data))
send_msg(c, bytes(json.dumps({"response": [times, data]}), encoding="utf-8"))
time.sleep(60)
except Exception:
exit(0) if __name__ == "__main__":
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 10083))
sock.listen(5)
t = threading.Thread(target=handler_accept(sock))
t.start()

我们继续改进一个案例,实现一个批量命令行执行器,我们使用xterm库实现Web命令行,用Jquery向后端发送数据,该工具前端代码如下。

<html>
<head>
<meta charSet="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="https://cdn.lyshark.com/xterm/xterm.css"/>
<script src="https://cdn.lyshark.com/xterm/xterm.js"></script>
<script src="https://cdn.lyshark.com/jquery/3.5.1/jquery.min.js" type="text/javascript"></script>
</head>
<body> <div id="terminal"></div> <input type="text" id="address" placeholder="主机范围 127.0.0.1-100" style="width:200px;height:40px"/>
<input type="text" id="command" placeholder="执行命令 ls -lh " style="width:400px;height:40px"/>
<input type="button" id="send_message" value="批量执行"> <!--实现格式化字符串-->
<script type="text/javascript">
$.format = function(source, params)
{
if (arguments.length == 1) return function()
{
var args = $.makeArray(arguments);
args.unshift(source);
return $.format.apply(this, args);
};
if (arguments.length > 2 && params.constructor != Array)
{
params = $.makeArray(arguments).slice(1);
}
if (params.constructor != Array)
{
params = [params];
}
$.each(params,
function(i, n)
{
source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
});
return source;
};
</script> <!--打开终端,并开始执行命令-->
<script type="text/javascript">
$(function(){
var window_width = $(window).width()-200;
var window_height = $(window).height()-300;
var term = new Terminal(
{
cols: Math.floor(window_width/9),
rows: Math.floor(window_height/20),
convertEol: true,
cursorBlink:false,
}); var sock = new WebSocket("ws://127.0.0.1:10083");
sock.onopen = function () {
term.open(document.getElementById('terminal'));
console.log('WebSocket Open');
}; sock.onmessage = function (recv) {
var data = JSON.parse(recv.data);
console.log(data['addr'] + ' -- ' + data['status']); var temp = "\x1B[1;3;35m 地址:[ {0} ] \x1B[0m --> \x1B[1;3;33m 状态:[ {1} ] \x1B[0m";
var string = $.format(temp, data['addr'],data['status']);
term.writeln(string);
}; $('#send_message').click(function () {
var message ={"address":null,"command":null};
message['address'] = $("#address").val();
message['command'] = $("#command").val();
var send_data = JSON.stringify(message);
window.sock.send(send_data);
});
window.sock = sock;
});
</script>

后端代码需要增加一个CalculationIP来计算IP地址范围,其他的地方保持不变.

def CalculationIP(Addr_Count):
ret = []
try:
IP_Start = str(Addr_Count.split("-")[0]).split(".")
IP_Heads = str(IP_Start[0] + "." + IP_Start[1] + "." + IP_Start[2] +".")
IP_Start_Range = int(Addr_Count.split(".")[3].split("-")[0])
IP_End_Range = int(Addr_Count.split("-")[1])
for item in range(IP_Start_Range,IP_End_Range+1):
ret.append(IP_Heads+str(item))
return ret
except Exception:
return 0 def handler_msg(conn):
with conn as c:
while True:
try:
ref_json = eval(recv_msg(c))
address = ref_json.get("address")
command = ref_json.get("command") address_list = CalculationIP(address)
for ip in address_list:
response = {'addr': ip, 'status': 'success'}
print("对主机: {} --> 执行: {}".format(ip,command))
send_msg(c, bytes(json.dumps(response) , encoding="utf-8"))
time.sleep(1)
except Exception:
exit(0) if __name__ == "__main__":
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 10083))
sock.listen(5)
t = threading.Thread(target=handler_accept(sock))
t.start()

Python 实现 WebSocket 通信的更多相关文章

  1. [Python]通过websocket与jsclient通信

    站点大多使用HTTP协议通信.而HTTP是无连接的协议.仅仅有client请求时,server端才干发出对应的应答.HTTP请求的包也比較大,假设仅仅是非常小的数据通信.开销过大.于是,我们能够使用w ...

  2. python 实现websocket

    python中websocket需要我们自己实现握手代码,流程是这样:服务端启动websocket服务,并监听.当客户端连接过来时,(需要我们自己实现)服务端就接收客户端的请求数据,拿到请求头,根据请 ...

  3. Python3+WebSockets实现WebSocket通信

    一.说明 1.1 背景说明 前段时间同事说云平台通信使用了个websocket的东西,今天抽空来看一下具体是怎么个通信过程. 从形式上看,websocket是一个应用层协议,socket是数据链路层. ...

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

    一.开始的话 使用python简单的实现websocket服务器,可以在浏览器上实时显示远程服务器的日志信息. 之前做了一个web版的发布系统,但没实现在线看日志,每次发布版本后,都需要登录到服务器上 ...

  5. C#(SuperWebSocket)与websocket通信

    原文:C#(SuperWebSocket)与websocket通信 客户端代码 点击可以查看一些关于websocket的介绍 <!DOCTYPE html> <html> &l ...

  6. js判断是否安装某个android app,没有安装下载该应用(websocket通信,监听窗口失去焦点事件)

    现在经常有写场景需要提示用户下载app, 但是如果用户已经安装,我们希望是直接打开app. 实际上,js是没有判断app是否已经安装的方法的,我们只能曲线救国. 首先,我们需要有call起app的sc ...

  7. Springboot集成WebSocket通信全部代码,即扣即用。

    websocket通信主要来自两个类以及一个测试的html页面. MyHandler 和 WebSocketH5Config,下面全部代码 MyHandler类全部代码: package com.un ...

  8. 【Java Web开发学习】Spring MVC整合WebSocket通信

    Spring MVC整合WebSocket通信 目录 ========================================================================= ...

  9. websocket通信1009错误,

    问题说明: springboot继承 WebSocketConfigurer实现websocket通信服务,服务器端报错,"The decoded text message was too ...

  10. python 实现 websocket

    一.websocket概要: websocket是基于TCP传输层协议实现的一种标准协议(关于网络协议,可以看看文末的图片),用于在客户端和服务端双向传输数据 传统的客户端想要知道服务端处理进度有两个 ...

随机推荐

  1. 【每日一题】31.「土」秘法地震 (二维前缀和 / DP)

    补题链接:Here 题意就是要找每一个 \(k * k\) 的小正方形里至少有一个1的数量 显然我们可以通过二维前缀和处理出(1, 1) 到 (n, m) 的数量 然后通过枚举处理出答案,具体思想是容 ...

  2. Educational Codeforces Round 104 (Rated for Div. 2) A-E 个人题解

    比赛链接 1487A. Arena n 个 Hero,分别有 \(a_i\) 的初始等级.每次两个 Hero 战斗时:等级相同无影响,否则等级高的英雄等级+1.直到某个英雄等级到了 \(100^{50 ...

  3. Android 加载图片占用内存分析

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/aRDzmMlkqB14Ty67GJs9vg作者:Xu Jie 不同Android版本,对一张图 ...

  4. 关于 Serverless 应用架构对企业价值的一些思考

    作者:寒斜 前言 对于企业方而言,最关心的核心诉求就是如何能获取更多的营收,更高的利润,通俗点说就是如何赚更多的钱:企业赚钱的方式主要是通过出售企业服务,当用户购买更多的企业服务,企业赚的钱就越多:而 ...

  5. 九、dockerfile指令讲解

    系列导航 一.docker入门(概念) 二.docker的安装和镜像管理 三.docker容器的常用命令 四.容器的网络访问 五.容器端口转发 六.docker数据卷 七.手动制作docker镜像 八 ...

  6. KSP(坎巴拉)萌新感悟

    1.为什么降落月球等无大气星球时减速为亚轨道之后便不再减速,等到快坠落的时候在满节流阀极限减速最省燃料? 因为我们的dv是确定的,燃料能给我们带来一定量的冲量,却因此可以带来不一定量的动量,显然速度越 ...

  7. Liunx常用操作(九)-进阶命令

    一.查看用户who 1.查看所有用户:who

  8. Linux: CPU C-states

    0. Overview There are various power modes of the CPU which are determined based on their current usa ...

  9. 如何使用Markdown编写笔记

    Markdown是什么? Markdown 是一种轻量级标记语言,创始人为约翰·格鲁伯(John Gruber). 它允许人们使用易读易写的纯文本格式编写文档,然后转换成有效的 XHTML(或者HTM ...

  10. 使用XStream,XMLSerializer 解析及格式转换

    博主原创,转载请注明出处 1.引入对应的maven依赖: <!--xstream--> <dependency> <groupId>com.thoughtworks ...