1、说明
1.1 背景说明
前段时间同事说云平台通讯使用了个websocket的东西,今天抽空来看一下具体是怎么个通讯过程。javascript
从形式上看,websocket是一个应用层协议,socket是数据链路层、网络层、传输层的抽像;从应用场合上看,websocket可使用javascript实现,而socket不能用javascript实现(真不能吗?我不太肯定);从实际效果上看,和通常的socket链接用起来没什么区别。html
咱们知道http是短链接的,反复创建和销毁链接比较耗费资源,另外http协议常常头部内容比主体内容还长也比较浪费资源;websocket能够认为就是一个内容使用载荷固定格式的socket长链接。html5
websocket基本协议格式以下,更多说明见RFC 6455:java
1.2 环境说明
当前环境我使用Python3+WebSockets库,WebSockets直接使用pip安装便可:python
pip install websockets
2、代码实现
长链接是有状态的,因此通常在且只在最开始进行一次身份认证,然后通讯过程不须要认证信息。咱们这里实现一个简单的用户名密码认证过程。长链接更多内容可参考“长链接与短链接的安全差别讨论 ”。git
另外,注意把代码中的ip改为本身的。github
2.1 python服务端代码
import asyncio import websockets # 检测客户端权限,用户名密码经过才能退出循环 async def check_permit(websocket): while True: recv_str = await websocket.recv() cred_dict = recv_str.split(":") if cred_dict[0] == "admin" and cred_dict[1] == "123456": response_str = "congratulation, you have connect with server\r\nnow, you can do something else" await websocket.send(response_str) return True else: response_str = "sorry, the username or password is wrong, please submit again" await websocket.send(response_str) # 接收客户端消息并处理,这里只是简单把客户端发来的返回回去 async def recv_msg(websocket): while True: recv_text = await websocket.recv() response_text = f"your submit context: {recv_text}" await websocket.send(response_text) # 服务器端主逻辑 # websocket和path是该函数被回调时自动传过来的,不须要本身传 async def main_logic(websocket, path): await check_permit(websocket) await recv_msg(websocket) # 把ip换成本身本地的ip start_server = websockets.serve(main_logic, '10.10.6.91', 5678) # 若是要给被回调的main_logic传递自定义参数,可以使用如下形式 # 1、修改回调形式 # import functools # start_server = websockets.serve(functools.partial(main_logic, other_param="test_value"), '10.10.6.91', 5678) # 修改被回调函数定义,增长相应参数 # async def main_logic(websocket, path, other_param) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()
2.2 python版客户端代码
import asyncio import websockets # 向服务器端认证,用户名密码经过才能退出循环 async def auth_system(websocket): while True: cred_text = input("please enter your username and password: ") await websocket.send(cred_text) response_str = await websocket.recv() if "congratulation" in response_str: return True # 向服务器端发送认证后的消息 async def send_msg(websocket): while True: _text = input("please enter your context: ") if _text == "exit": print(f'you have enter "exit", goodbye') await websocket.close(reason="user exit") return False await websocket.send(_text) recv_text = await websocket.recv() print(f"{recv_text}") # 客户端主逻辑 async def main_logic(): async with websockets.connect('ws://10.10.6.91:5678') as websocket: await auth_system(websocket) await send_msg(websocket) asyncio.get_event_loop().run_until_complete(main_logic())
2.3 html版客户端代码
html版客户端代码,只能经过回调函数接收服务端返回的数据,不能主动接收,感受怪怪的。web


<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>websocket通讯客户端</title> <script type="text/javascript"> function WebSocketTest() { if ("WebSocket" in window) { // 打开一个 web socket var ws = new WebSocket("ws://10.10.6.91:5678"); // 链接创建后的回调函数 ws.onopen = function() { // Web Socket 已链接上,使用 send() 方法发送数据 ws.send("admin:123456"); alert("正在发送:admin:123456"); }; // 接收到服务器消息后的回调函数 ws.onmessage = function (evt) { var received_msg = evt.data; if (received_msg.indexOf("sorry") == -1) { alert("收到消息:"+received_msg); } }; // 链接关闭后的回调函数 ws.onclose = function() { // 关闭 websocket alert("链接已关闭..."); }; } else { // 浏览器不支持 WebSocket alert("您的浏览器不支持 WebSocket!"); } } </script> </head> <body onload="WebSocketTest()"> </body> </html>
3、通讯数据包截获及通讯过程分析
如下数据包其于上边的python服务端和html版客户端,再次强调注意把代码中的ip改为本身电脑当前的ip。浏览器
3.1 wireshark通讯数据包截获及通讯过程分析
wireshark拦截数据包后可以使用过滤器表达式“websocket”进行过滤:安全
咱们追踪数据流能够更清晰地看清websocket的通讯过程,能够看到先是用http完成了创建链接,而后切换到websocket协议。
再看具体数据流也确实如此,先用两个http包创建链接,然后是websocket通讯(问题是不清楚websocket内容是怎么编码的,有些就显示不了原始内容)
3.2 burpsuite通讯数据包截获及通讯过程分析
和正常配置代理便可,burpsuite能识别和拦截websocket数据包,以下图:
不过和通常http请求有区别的是,websocket请求会被单独汇总到“WebSockets history”选项卡,而不是和http请求混在“HTTP history”选项卡。
3.3 开发者工具通讯数据包截获及通讯过程分析
Firefox开发者工具只能看到创建websocket链接的两个http数据包,没看到怎么查看具体传输内容,Chrome开发者工具Frames选项卡能够,以下:
参考:
https://websockets.readthedocs.io/en/stable/intro.html