Websocket - Websocket原理(握手、解密、加密)、基于Python实现简单示例
一、Websocket原理(握手、解密、加密)
WebSocket协议是基于TCP的一种新的协议。WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符。它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。
本文将使用Python编写Socket服务端,一步一步分析请求过程!!!
1、启动服务端
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 9527))
sock.listen(5)
# 获取客户端socket对象,并等待用户连接
conn, address = sock.accept()
......
......
......
启动Socket服务器后,等待用户【连接】,然后进行收发数据。
2、客户端连接
<script type="text/javascript">
var socket = new WebSocket("ws://127.0.0.1:9527/xxoo");
......
</script>
当客户端向服务端发送连接请求时,不仅连接还会发送【握手】信息,并等待服务端响应,至此连接才创建成功!
3、建立连接【握手】
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 9527))
sock.listen(5)
# 获取客户端socket对象,并等待用户连接
conn, address = sock.accept()
# 获取客户端的【握手】信息,data即握手信息
data = conn.recv(1024)
......
......
conn.send('响应【握手】信息')
请求和响应的【握手】信息需要遵循规则:
- 从请求【握手】信息中提取 Sec-WebSocket-Key;
- 利用magic_string 和 Sec-WebSocket-Key 进行hmac1加密,再进行base64加密;
- 将加密结果响应给客户端;
注:magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11
请求【握手】信息为:
b'GET /xxoo HTTP/1.1\r\n
Host: 127.0.0.1:9527\r\n
Connection: Upgrade\r\n
Pragma: no-cache\r\n
Cache-Control: no-cache\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36\r\n
Upgrade: websocket\r\n
Origin: http://localhost:63342\r\n
Sec-WebSocket-Version: 13\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
Cookie: session=ba01367c-59b9-41d4-81ba-30b70db282c6\r\n
Sec-WebSocket-Key: jLSLU57WxRJACRQxlN47Tw==\r\n
Sec-WebSocket-Extensions: permessage-deflate;
......'
提取Sec-WebSocket-Key值并加密:
import socket
import base64
import hashlib def get_headers(data):
"""
将请求头格式化成字典
:param data: 请求头
:return: 请求头信息字典
"""
header_dict = {}
header_str = data.decode("utf8")
for i in header_str.split("\r\n"):
if str(i).startswith("Sec-WebSocket-Key"):
header_dict["Sec-WebSocket-Key"] = i.split(":")[1].strip() return header_dict sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 9527))
sock.listen(5)
# 获取客户端socket对象,并等待用户连接
conn, address = sock.accept()
# 获取客户端的【握手】信息
data = conn.recv(1024)
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://127.0.0.1:9527\r\n\r\n"
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
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'))
# 响应【握手】信息
conn.send(response_str.encode("utf-8"))
......
......
至此,客户端与服务端完成握手过程。
4、客户端与服务端收发数据
客户端和服务端传输数据时,需要对数据进行【封包】和【解包】。客户端的JavaScript类库已经封装【封包】和【解包】过程,但Socket服务端需要手动实现。
第一步:获取客户端发送的数据【解包】
# b'\x81\x86#\xa47\x93\xc7\x19\x97v\x86\x19' 你好 hashstr = b'\x81\x83\xceH\xb6\x85\xffz\x85'
# b'\x81 \x86# \xa47\x93\xc7\x19 \x97v\x86\x19' # 将第二个字节 \x86# 也就是第9-16位 与 127 进行位运算
payload_len = hashstr[1] & 127 if payload_len == 127:
extend_payload_len = hashstr[2:10]
mask = hashstr[10:14]
decoded = hashstr[14:]
# 当位运算结果等于127时,则第3-10个字节为数据长度
# 第11-14字节为mask ,即解密所需字符串
# 则数据为第15字节至结尾 if payload_len == 126:
extend_payload_len = hashstr[2:4]
mask = hashstr[4:8]
decoded = hashstr[8:]
# 当位运算结果等于126时,则第3-4个字节为数据长度
# 第5-8字节为mask ,即解密所需字符串
# 则数据为第9字节至结尾 if payload_len <= 125:
extend_payload_len = None
mask = hashstr[2:6]
decoded = hashstr[6:]
# 当位运算结果小于等于125时,则这个数字就是数据的长度
# 第3-6字节为mask ,即解密所需字符串
# 则数据为第7字节至结尾 str_byte = bytearray() for i in range(len(decoded)): # 循环数据长度
byte = decoded[i] ^ mask[i % 4] # ^ 是或运算
str_byte.append(byte) print(str_byte.decode("utf8"))
解包详细过程:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
第二步:向客户端发送数据【封包】
def send_msg(conn, msg_bytes):
"""
WebSocket服务端向客户端发送消息
:param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()
:param msg_bytes: 向客户端发送的字节
:return:
"""
import struct token = b"\x81"
length = len(msg_bytes)
if length < 126:
token += struct.pack("B", length)
elif length == 126:
token += struct.pack("!BH", 126, length)
else:
token += struct.pack("!BQ", 127, length) msg = token + msg_bytes
conn.send(msg)
return True
二、基于Python实现简单示例
1、基于Python socket实现的WebSocket服务端
# !/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import base64
import hashlib def get_headers(data):
"""
将请求头格式化成字典
:param data: 请求头
:return: 请求头信息字典
"""
header_dict = {}
header_str = data.decode("utf8")
for i in header_str.split("\r\n"):
if str(i).startswith("Sec-WebSocket-Key"):
header_dict["Sec-WebSocket-Key"] = i.split(":")[1].strip() return header_dict def send_msg(conn, msg_bytes):
"""
WebSocket服务端向客户端发送消息
:param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()
:param msg_bytes: 向客户端发送的字节
:return:
"""
import struct token = b"\x81"
length = len(msg_bytes)
if length < 126:
token += struct.pack("B", length)
elif length == 126:
token += struct.pack("!BH", 126, length)
else:
token += struct.pack("!BQ", 127, length) msg = token + msg_bytes
conn.send(msg)
return True def run():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 9527))
sock.listen(5) conn, address = sock.accept()
data = conn.recv(1024)
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://127.0.0.1:9527\r\n\r\n"
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
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'))
# 响应【握手】信息
conn.send(response_str.encode("utf-8")) while True:
try:
info = conn.recv(8096)
except Exception as e:
info = None
if not info:
break
payload_len = info[1] & 127
if payload_len == 126:
extend_payload_len = info[2:4]
mask = info[4:8]
decoded = info[8:]
elif payload_len == 127:
extend_payload_len = info[2:10]
mask = info[10:14]
decoded = info[14:]
else:
extend_payload_len = None
mask = info[2:6]
decoded = info[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')
send_msg(conn, body.encode('utf-8')) sock.close() if __name__ == '__main__':
run()
2、利用JavaScript类库实现客户端
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div>
<input type="text" id="txt"/>
<input type="button" id="btn" value="提交" onclick="sendMsg();"/>
<input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
</div>
<div id="content"></div> <script type="text/javascript">
var socket = new WebSocket("ws://127.0.0.1:9527/chatsocket"); socket.onopen = function () {
/* 与服务器端连接成功后,自动执行 */ var newTag = document.createElement('div');
newTag.innerHTML = "【连接成功】";
document.getElementById('content').appendChild(newTag);
}; socket.onmessage = function (event) {
/* 服务器端向客户端发送数据时,自动执行 */
var response = event.data;
var newTag = document.createElement('div');
newTag.innerHTML = response;
document.getElementById('content').appendChild(newTag);
}; socket.onclose = function (event) {
/* 服务器端主动断开连接时,自动执行 */
var newTag = document.createElement('div');
newTag.innerHTML = "【关闭连接】";
document.getElementById('content').appendChild(newTag);
}; function sendMsg() {
var txt = document.getElementById('txt');
socket.send(txt.value);
txt.value = "";
}
function closeConn() {
socket.close();
var newTag = document.createElement('div');
newTag.innerHTML = "【关闭连接】";
document.getElementById('content').appendChild(newTag);
} </script>
</body>
</html>
更多websocket详解参见:http://www.runoob.com/html/html5-websocket.html
websocket原理参考博客:https://www.cnblogs.com/fuqiang88/p/5956363.html
Websocket - Websocket原理(握手、解密、加密)、基于Python实现简单示例的更多相关文章
- Flask-session,WTForms,POOL,Websocket通讯原理 -握手,加密解密过程
1.Flask-session Flask中的session 需要执行 session_interface - open_session存储到redis中,存的key:session:d3f07db2 ...
- Python Thrift 简单示例
本文基于Thrift-0.10,使用Python实现服务器端,使用Java实现客户端,演示了Thrift RPC调用示例.Java客户端提供两个字符串参数,Python服务器端计算这两个字符串的相似度 ...
- python psutil简单示例
python psutil简单示例 利用psutil编写简单的检测小脚本 0.安装psutil模块 ...
- flask 第五章 WebSocket GeventWebsocket 单聊群聊 握手 解密 加密
1.WebSocket 首先我们来回顾一下,我们之前用socket学习过的项目有: 1.django 2.flask 3.FTP - 文件服务 HTTP - TCP (特点): 1.一次请求,一次响应 ...
- 基于python实现简单web服务器
做web开发的你,真的熟悉web服务器处理机制吗? 分析请求数据 下面是一段原始的请求数据: b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8000\r\nConnectio ...
- 基于Python 的简单推荐系统
def loadExData(): return[[1,1,1,0,0], [2,2,2,0,0], [1,1,1,0,0], [5,5,5,0,0], [1,1,0,2,2], [0,0,0,3,3 ...
- python gRPC简单示例
Ubuntu18.04安装gRPC protobuf-compiler-grpc安装 sudo apt-get install protobuf-compiler-grpc protobuf-comp ...
- WebSocket的原理,以及和Http的关系
一.WebSocket是HTML5中的协议,支持持久连接:而Http协议不支持持久连接. 首先HTMl5指的是一系列新的API,或者说新规范,新技术.WebSocket是HTML5中新协议.新API. ...
- WebSocket的原理,以及和Http的关系 (转载)
一.WebSocket是HTML5中的协议,支持持久连接:而Http协议不支持持久连接. 首先HTMl5指的是一系列新的API,或者说新规范,新技术.WebSocket是HTML5中新协议.新API. ...
随机推荐
- love2d--glsl02变量和语句
Shader分为顶点着色器和片段着色器,GPU先处理顶点再处理片段,大概可以这么理解, 顶点着色器处理模型里的点,输出处理后的数据,这些数据经过GPU其它模块处理后传入 片段着色器,经片段着色器综合后 ...
- java 读取Zip文件进行写入
直接读取ZIp文件读取写入到别的文件中. package jp.co.misumi.mdm.batch; import java.io.BufferedReader; import java.io.F ...
- Unix系统编程()发送信号kill
与shell的kill命令类似,一个进程能够使用kill系统调用向另一进程发送信号. 之所以选择kill作为术语,因为早期UNIX实现中大多数信号的默认行为是终止进程. #include <si ...
- [Linux内核]软中断、tasklet、工作队列
转自:http://www.cnblogs.com/li-hao/archive/2012/01/12/2321084.html 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制, ...
- linux 域和xenomai 实时域之间的交互
/* * XDDP-based RT/NRT threads communication demo. * * Real-time Xenomai threads and regular Linux t ...
- 深入分析 iBATIS 框架之系统架构与映射原理
iBATIS 框架主要的类层次结构 总体来说 iBATIS 的系统结构还是比较简单的,它主要完成两件事情: 根据 JDBC 规范建立与数据库的连接: 通过反射打通 Java 对象与数据库参数交互之间相 ...
- 线段树 + 字符串Hash - Codeforces 580E Kefa and Watch
Kefa and Watch Problem's Link Mean: 给你一个长度为n的字符串s,有两种操作: 1 L R C : 把s[l,r]全部变为c; 2 L R d : 询问s[l,r]是 ...
- 【BZOJ】1639: [Usaco2007 Mar]Monthly Expense 月度开支(二分)
http://www.lydsy.com/JudgeOnline/problem.php?id=1639 同tyvj1359,http://www.cnblogs.com/iwtwiioi/p/394 ...
- C# 将MSMQ消息转换成Json格式
PS:主要就是一个配置文件和一个转换函数 配置文件app.config 之前要ADD reference --> system.configuration & using.syst ...
- error: Please reinstall the libcurl distribution - easy.h should be in <curl-dir>/include/curl/
运行php-5.3.10 --enable-mbstring --enable-ftp --enable-gd-native-ttf --with-openssl --enable-pcntl --e ...