WebSocket小试牛刀
一. 为什么需要WebSocket?
1.1 初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?
WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)首先HTTP有1.1和1.0之说,也就是所谓的keep-alive,把多个HTTP请求合并为一个,但是Websocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充.
答案其实很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起,特别是短链接,无状态,请求在获取相应信息之后立刻断开连接.
1.2 那么,WebSocet到底是什么呢?它能带来什么好处?
Websocket是什么样的协议,具体有什么优点首先,Websocket是一个持久化的协议,相对于HTTP这种非持久的协议来说。
简单的举个例子吧,用目前应用比较广泛的PHP生命周期来解释。
- HTTP的生命周期通过Request来界定,也就是一个Request 一个Response,那么在HTTP1.0中,这次HTTP请求就结束了。
- 在HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。但是请记住 Request = Response,在HTTP中永远是这样,也就是说一个request只能有一个response。而且这个response也是被动的,不能主动发起。
1.3 教练,你BB了这么多,跟Websocket有什么关系呢?
好吧,我正准备说Websocket呢。。首先Websocket是基于HTTP协议的,或者说借用了HTTP的协议来完成一部分握手。在握手阶段是一样的,以下涉及专业技术内容,不想看的可以跳过.
首先我们来看个典型的Websocket握手
1 | GET /chat HTTP/1.1 |
熟悉HTTP的童鞋可能发现了,这段类似HTTP协议的握手请求中,多了几个东西。
1 | Upgrade: websocket |
这个就是Websocket的核心了,告诉Apache、Nginx等服务器:注意啦,窝发起的是Websocket协议,快点帮我找到对应的助理处理~不是那个老土的HTTP:)
1 | Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== |
首先,Sec-WebSocket-Key 是一个Base64 encode的值,这个是浏览器随机生成的,告诉服务器:泥煤,不要忽悠窝,我要验证尼是不是真的是Websocket助理。然后,Sec_WebSocket-Protocol 是一个用户定义的字符串,用来区分同URL下,不同的服务所需要的协议。简单理解:今晚我要服务A,别搞错啦最后,Sec-WebSocket-Version 是告诉服务器所使用的Websocket Draft(协议版本),在最初的时候,Websocket协议还在 Draft 阶段,各种奇奇怪怪的协议都有,而且还有很多期奇奇怪怪不同的东西,什么Firefox和Chrome用的不是一个版本之类的,当初Websocket协议太多可是一个大难题。。不过现在还好,已经定下来啦大家都使用的一个东西~ 脱水:服务员,我要的是13岁的噢→_→
然后服务器会返回下列东西,表示已经接受到请求, 成功建立Websocket啦!
1 | HTTP/1.1 101 Switching Protocols |
这里开始就是HTTP最后负责的区域了,告诉客户,我已经成功切换协议啦~
1 | Upgrade: websocket |
依然是固定的,告诉客户端即将升级的是Websocket协议,而不是mozillasocket,lurnarsocket或者shitsocket。然后,Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key。服务器:好啦好啦,知道啦,给你看我的ID CARD来证明行了吧。。后面的,Sec-WebSocket-Protocol 则是表示最终使用的协议。至此,HTTP已经完成它所有工作了,接下来就是完全按照Websocket协议进行了。
⚠️ : WebSocket两个重要的流程: 1. 握手; 2. 加密;
二. 剖析WebSocket请求流程
下面讲使用Python编写Socket服务端,一步一步分析请求过程!!!
2.1 服务端
1 | import socket |
启动Socket服务器后,等待用户【连接】,然后进行收发数据。
2.2 客户端连接
1 | <!DOCTYPE html> |
当客户端向服务端发送连接请求时,不仅连接还会发送[握手]信息,并等待服务端响应,至此连接才创建成功!
2.3 建立连接
1 | import socket |
请求和响应的【握手】信息需要遵循规则:
- 从请求【握手】信息中提取 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
10GET /chatsocket HTTP/1.1
Host: 127.0.0.1:8002
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:63342
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits2.4 提取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
44
45
46
47
48
49
50import socket
import base64
import hashlib
def get_headers(data):
"""
将请求头格式化成字典
:param data:
:return:
"""
header_dict = {}
data = str(data, encoding='utf-8')
for i in data.split('\r\n'):
print(i)
header, body = data.split('\r\n\r\n', 1)
header_list = header.split('\r\n')
for i in range(0, len(header_list)):
if i == 0:
if len(header_list[i].split(' ')) == 3:
header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
else:
k, v = header_list[i].split(':', 1)
header_dict[k] = v.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', 8002))
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://%s%s\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'), headers['Host'], headers['url'])
# 响应【握手】信息
conn.send(bytes(response_str, encoding='utf-8'))
...
...
...
三. 客户端和服务端收发数据
客户端和服务端传输数据时,需要对数据进行【封包】和【解包】。客户端的JavaScript类库已经封装【封包】和【解包】过程,但Socket服务端需要手动实现。
3.1 第一步:获取客户端发送的数据【解包】
1 | # 基于python实现的解包 |
3.2 解包详细过程:
1 | 0 1 2 3 |
3.2.1 官方解释
1 | The MASK bit simply tells whether the message is encoded. Messages from the client must be masked, so your server should expect this to be 1. (In fact, section 5.1 of the spec says that your server must disconnect from a client if that client sends an unmasked message.) When sending a frame back to the client, do not mask it and do not set the mask bit. We'll explain masking later. Note: You have to mask messages even when using a secure socket.RSV1-3 can be ignored, they are for extensions. |
3.3 向客户端发送数据【封包】
1 | def send_msg(conn, msg_bytes): |
四. 基于python实现简单示例
4.1 基于Python socket实现的WebSocket服务端
1 | #! /usr/bin/env python |
4.2 利用JavaScript类库实现客户端
1 | <!DOCTYPE html> |
此时,我们在浏览器的Console控制台利用Socket对象发送消息,将会收到返回信息
五. 基于Tornado框架实现Web聊天室
Tornado是一个支持WebSocket的优秀框架,其内部原理正如1~5步骤描述,当然Tornado内部封装功能更加完整。
以下是基于Tornado实现的聊天室示例:
1 | # cat app.py |
cat index.html
</div>
<script src="/static/jquery-2.1.4.min.js"></script>
<script type="text/javascript">
$(function () {
wsUpdater.start();
});
var wsUpdater = {
socket: null,
uid: null,
start: function() {
var url = "ws://127.0.0.1:8888/chat";
wsUpdater.socket = new WebSocket(url);
wsUpdater.socket.onmessage = function(event) {
console.log(event);
if(wsUpdater.uid){
wsUpdater.showMessage(event.data);
}else{
wsUpdater.uid = event.data;
}
}
},
showMessage: function(content) {
$('#container').append(content);
}
};
function sendMsg() {
var msg = {
uid: wsUpdater.uid,
message: $("#txt").val()
};
wsUpdater.socket.send(JSON.stringify(msg));
}
```
本文标题:WebSocket小试牛刀
文章作者:shuke
发布时间:2020年04月20日 - 14:04
最后更新:2020年04月20日 - 14:04
原始链接:https://shuke163.github.io/2020/04/20/WebSocket%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/
许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。
本文标题:WebSocket小试牛刀
文章作者:shuke
发布时间:2020年04月20日 - 14:04
最后更新:2020年04月20日 - 14:04
原始链接:https://shuke163.github.io/2020/04/20/WebSocket%E5%B0%8F%E8%AF%95%E7%89%9B%E5%88%80/
许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。