WebsSocket
一、Websocket原理(握手、解密、加密)
WebSocket协议是基于TCP的一种新的协议。WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符。它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。
本文将使用Python编写Socket服务端,一步一步分析请求过程!!!
1、启动服务端
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
 | 
import socketsock = 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、客户端连接
| 
 1 
2 
3 
4 
 | 
<script type="text/javascript">    var socket = new WebSocket("ws://127.0.0.1:9527/xxoo");    ......</script> | 
当客户端向服务端发送连接请求时,不仅连接还会发送【握手】信息,并等待服务端响应,至此连接才创建成功!
3、建立连接【握手】
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
 | 
import socketsock = 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
请求【握手】信息为:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
 | 
b'GET /xxoo HTTP/1.1\r\nHost: 127.0.0.1:9527\r\nConnection: Upgrade\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nUser-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\nUpgrade: websocket\r\nOrigin: http://localhost:63342\r\nSec-WebSocket-Version: 13\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: session=ba01367c-59b9-41d4-81ba-30b70db282c6\r\nSec-WebSocket-Key: jLSLU57WxRJACRQxlN47Tw==\r\nSec-WebSocket-Extensions: permessage-deflate; ......' | 
提取Sec-WebSocket-Key值并加密:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
 | 
import socketimport base64import hashlibdef 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_dictsock = 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_stringac = 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服务端需要手动实现。
第一步:获取客户端发送的数据【解包】
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
 | 
# 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] & 127if 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")) | 
解包详细过程:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
 | 
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 ...                |+---------------------------------------------------------------+ | 
第二步:向客户端发送数据【封包】
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
 | 
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服务端
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
92 
93 
94 
95 
96 
97 
98 
99 
100 
 | 
# !/usr/bin/env python# -*- coding:utf-8 -*-import socketimport base64import hashlibdef 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_dictdef 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 Truedef 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类库实现客户端
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
 | 
<!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> | 
参考博客(一):http://www.runoob.com/html/html5-websocket.html
参考博客(二):https://www.cnblogs.com/fuqiang88/p/5956363.html
参考博客(三):https://baijiahao.baidu.com/s?id=1617829980923673614&wfr=spider&for=pc
WebsSocket的更多相关文章
- webssocket简介-服务器可以主动传送数据给浏览器
		
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并 ...
 - 7.1.1.关闭WebSocket连接
		
7.1.定义 7.1.1.关闭WebSocket连接 为_关闭WebSocket连接_,端点需关闭底层TCP连接.端点应该使用一个方法完全地关闭TCP连接,以及TLS会话,如果合适,丢弃任何可能已经接 ...
 - 开源的物联网技术平台(Thingsboard)
		
1 总体说明 1.1 产品概述 1.1.1 Thingsboard作用 1.置备并控制设备. 2.采集设备数据并进行数据可视化. 3.分析设备数据,触发告警. 4.将数据传输到另一个系统. 5 ...
 
随机推荐
- BCB6 使用TZCompressionStream压缩
			
最近由于项目需要涉及到解压第三方公司的数据,在此做一下记录环境部署和使用方法,免得以后忘记. 对方公司的数据是通过TCompressionStream 压缩之后,存到数据库中,采用的 ...
 - Codeforces Round #584 B. Koala and Lights
			
链接: https://codeforces.com/contest/1209/problem/B 题意: It is a holiday season, and Koala is decoratin ...
 - 【安卓基础】WebView开发优化基础
			
最近工作很忙,不仅要抽空进行管理,还有开发任务在身,幸好有一些规划进行指导,所以还能顺利解决问题.在管理和技术上面,我认为技术是硬实力,管理是软实力,自己需要多点时间花在技术上. 回归正题,在项目中的 ...
 - Codeforces Round #442 (Div. 2) B题【一道模拟题QAQ】
			
B. Nikita and string One day Nikita found the string containing letters "a" and "b&qu ...
 - E:last-child
			
E:last-child 语法: E:last-child { sRules } 说明: 匹配父元素的最后一个子元素E.大理石平台厂家大理石平台厂家 要使该属性生效,E元素必须是某个元素的子元素,E的 ...
 - PHP mysqli_change_user() 函数
			
改变指定数据库连接的用户: <?php $con=mysqli_connect("localhost","my_user","my_passwo ...
 - mysql 常见ALTER TABLE操作
			
删除列 alter table table-name drop col-name; 增加列(单列) alter table table-name add col-name col-type comme ...
 - 【csp模拟赛3】bridge.cpp--矩阵加速递推
			
题目描述 穿越了森林,前方有一座独木桥,连接着过往和未来(连接着上一题和下一题...). 这座桥无限长. 小 Q 在独木桥上彷徨了.他知道,他只剩下了 N 秒的时间,每一秒的时间里,他会向 左或向右移 ...
 - kubernetes部署nginx/tomcat
			
kubernetes集群已经部署好了,需要的话可以参考之前的文章https://www.cnblogs.com/winter1519/p/10015420.html [root@master tomc ...
 - 2015-2016 ACM ICPC Baltic Selection Contest
			
这是上礼拜三的训练赛,以前做过一次,这次仅剩B题没补.题目链接:https://vjudge.net/contest/153192#overview. A题,水题. C题,树形DP,其实是一个贪心问题 ...