websocket抓包
我们可以用wireshark抓个包,实际看下数据包的情况。
客户端请求升级为websocket
上面这张图,注意画了红框的第2445
行报文,是websocket的第一次握手,意思是发起了一次带有特殊Header
的HTTP请求。
服务器同意升级为websocket协议
上面这个图里画了红框的4714
行报文,就是服务器在得到第一次握手后,响应的第二次握手,可以看到这也是个HTTP类型的报文,返回的状态码是101。同时可以看到返回的报文header中也带有各种websocket
相关的信息,比如Sec-WebSocket-Accept
。
两次HTTP请求之后正式使用websocket通信
上面这张图就是全貌了,从截图上的注释可以看出,websocket和HTTP一样都是基于TCP的协议。经历了三次TCP握手之后,利用HTTP协议升级为websocket协议。
你在网上可能会看到一种说法:"websocket是基于HTTP的新协议",其实这并不对,因为websocket只有在建立连接时才用到了HTTP,升级完成之后就跟HTTP没有任何关系了。
这就好像你喜欢的女生通过你要到了你大学室友的微信,然后他们自己就聊起来了。你能说这个女生是通过你去跟你室友沟通的吗?不能。你跟HTTP一样,都只是个工具人。
这就有点"借壳生蛋"的那意思。
HTTP和websocket的关系
websocket的消息格式
上面提到在完成协议升级之后,两端就会用webscoket的数据格式进行通信。
数据包在websocket中被叫做帧。
我们来看下它的数据格式长什么样子。
websocket报文格式
这里面字段很多,但我们只需要关注下面这几个。
opcode字段:这个是用来标志这是个什么类型的数据帧。比如。
- 等于1时是指text类型(
string
)的数据包 - 等于2是二进制数据类型(
[]byte
)的数据包 - 等于8是关闭连接的信号
payload字段:存放的是我们真正想要传输的数据的长度,单位是字节。比如你要发送的数据是字符串"111"
,那它的长度就是3
。
另外,可以看到,我们存放payload长度的字段有好几个,我们既可以用最前面的7bit
, 也可以用后面的7+16bit或7+64bit。
那么问题就来了。
我们知道,在数据层面,大家都是01二进制流。我怎么知道什么情况下应该读7bit,什么情况下应该读7+16bit呢?
websocket会用最开始的7bit做标志位。不管接下来的数据有多大,都先读最先的7个bit,根据它的取值决定还要不要再读个16bit或64bit。
- 如果
最开始的7bit
的值是 0~125,那么它就表示了 payload 全部长度,只读最开始的7个bit
就完事了。
payload长度在0到125之间
- 如果是
126(0x7E)
。那它表示payload的长度范围在126~65535
之间,接下来还需要再读16bit。这16bit会包含payload的真实长度。
payload长度在126到65535之间
- 如果是
127(0x7F)
。那它表示payload的长度范围>=65536
,接下来还需要再读64bit。这64bit会包含payload的长度。这能放2的64次方byte的数据,换算一下好多个TB,肯定够用了。
payload长度大于等于65536的情况
payload data字段:这里存放的就是真正要传输的数据,在知道了上面的payload长度后,就可以根据这个值去截取对应的数据。
大家有没有发现一个小细节,websocket的数据格式也是 数据头(内含payload长度) + payload data 的形式。
之前写的《既然有HTTP协议,为什么还要有RPC》提到过,TCP协议本身就是全双工,但直接使用纯裸TCP去传输数据,会有粘包的"问题"。为了解决这个问题,上层协议一般会用消息头+消息体的格式去重新包装要发的数据。
而消息头里一般含有消息体的长度,通过这个长度可以去截取真正的消息体。
HTTP协议和大部分RPC协议,以及我们今天介绍的websocket协议,都是这样设计的。
消息边界长度标志
websocket的使用场景
websocket完美继承了TCP协议的全双工能力,并且还贴心的提供了解决粘包的方案。它适用于需要服务器和客户端(浏览器)频繁交互的大部分场景。比如网页/小程序游戏,网页聊天室,以及一些类似飞书这样的网页协同办公软件。
回到文章开头的问题,在使用websocket协议的网页游戏里,怪物移动以及攻击玩家的行为是服务器逻辑产生的,对玩家产生的伤害等数据,都需要由服务器主动发送给客户端,客户端获得数据后展示对应的效果。
websocket的使用场景
总结
- TCP协议本身是全双工的,但我们最常用的HTTP1.1,虽然是基于TCP的协议,但它是半双工的,对于大部分需要服务器主动推送数据到客户端的场景,都不太友好,因此我们需要使用支持全双工的websocket协议。
- 在HTTP1.1里。只要客户端不问,服务端就不答。基于这样的特点,对于登录页面这样的简单场景,可以使用定时轮询或者长轮询的方式实现服务器推送(comet)的效果。
- 对于客户端和服务端之间需要频繁交互的复杂场景,比如网页游戏,都可以考虑使用websocket协议。
- websocket和socket几乎没有任何关系,只是叫法相似。
- 正因为各个浏览器都支持HTTP协议,所以websocket会先利用HTTP协议加上一些特殊的header头进行握手升级操作,升级成功后就跟HTTP没有任何关系了,之后就用websocket的数据格式进行收发数据。
最后
最近原创更文的阅读量稳步下跌,思前想后,夜里辗转反侧。
我有个不成熟的请求。
离开广东好长时间了,好久没人叫我靓仔了。
大家可以在评论区里,叫我一靓仔吗?
我这么善良质朴的愿望,能被满足吗?
如果实在叫不出口的话,能帮我点下关注和右下角的点赞+在看吗?