一、轮询和长轮询
在websocket协议出现之前,要想实现服务器和客户端的双向持久通信采取的是Ajax轮询。它的原理是每隔一段时间客户端就给服务器发送请求找服务器要数据。
让我们通过一个生活化的比喻来解释轮询和长轮询假设你正在与一位不怎么主动说话的老大爷(服务器)聊天,你想要知道他是否有新的故事或者信息分享给你。
(1)轮询
就像每隔几分钟你就跑到老大爷家门前敲门,问他:“大爷,您有新消息吗?”每次老大爷如果没有新内容,就会回答:“没有呢,孩子。”然后你失望地离开,过一会儿再回来重复同样的问题。这种方式下,你消耗了大量的精力(客户端资源),而且由于间隔时间的存在,获取新消息的延迟较高,并且无论是否获得新消息,每次询问都会给老大爷带来打扰(服务器负担)。
为了改善这种需要反复请求浪费带宽资源的情况,人们发明了长轮询,顾名思义长轮询是只客户端只需要发起一个HTTP请求,服务器一直保持连接打开,直到有消息时才返回响应;客户端在收到响应后立刻发起下一个请求,以此实现近似实时推送的效果。
(2)长轮询
现在改为采用长轮询的方式。你来到老大爷家,站在门口问:“大爷,如果有新消息,请随时告诉我。”然后你就耐心地等待在那儿,老大爷如果没消息就不回应,一旦有了新消息,就立即告诉你。这样,你不再频繁地往返于老大爷家门口,而是保持一个“半开放”的聊天状态。尽管你在门口等待的时间可能较长(长连接维持期间消耗资源),但比起频繁询问,减少了无效交互,提高了发现新消息的速度,同时降低了对老大爷的打扰频率(减轻了服务器负载)。然而,这个过程中你仍然不能做其他事情(浏览器线程被占用),直到老大爷给出答复或超时为止。
二、websocket协议
websocket是一个在单个 TCP 连接上进行全双工通讯的协议。 websocket允许服务端主动向客户端推送数据。客户端和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在实际的网络通信中,轮询和长轮询分别对应着客户端周期性地向服务器发送请求查询更新,以及客户端发起请求后保持连接打开等待服务器响应的机制。而websocket则更像是建立了一个实时通讯通道,你和老大爷之间架设了一部电话,只要有新消息,老大爷就能直接拨通你的电话进行通知,无需反复敲门询问。
三、websocket通信
(1)握手(第一次请求服务器)
在实际的websocket通信过程中,存在一个从HTTP协议升级到websocket协议的过程。这个过程通常称为WebSocket握手,它是通过一次特殊的HTTP请求来实现的。
当握手完成后,HTTP连接即被转换为WebSocket连接,之后双方就可以进行全双工、低延迟的数据传输了,不再使用HTTP报文格式,而是采用WebSocket帧结构进行数据封装和解封装。
(2)websocket的握手数据包长什么样的?
我们来看看数据包里面的请求头和响应头信息都是什么意思吧!重点关注红色的字段就行。
字段 | 解释 |
GET wss://live-ws-group10.kuaishou.com/websocket HTTP/1.1 | HTTP GET方法的请求,请求的目标资源是wss://live-ws-group10.kuaishou.com/websocket 。wss 表示这是一个安全的WebSocket连接(基于TLS加密)。 |
Host | 指定服务器的主机名和端口,用于确定目标服务地址。 |
Connection: Upgrade | 表明客户端希望升级当前的HTTP连接为另一个协议,这里是指WebSocket协议。 |
Pragma: no-cache | 告诉服务器不使用缓存机制,确保请求获取最新的数据。 |
Cache-Control: no-cache | 同样用于控制缓存策略,禁止缓存此请求的响应内容。 |
User-Agent | 描述客户端软件信息,这里是Chrome或Edge浏览器在Windows 10系统上的版本号。 |
Upgrade: websocket | 明确指出要将连接升级到WebSocket协议。 |
Origin | 标识本次WebSocket连接发起的源站点,这是同源策略中的一部分,用来说明请求是从哪个页面或者应用发起的。 |
Sec-WebSocket-Version | 客户端支持的WebSocket协议版本号,13是RFC 6455定义的最新版本。 |
Accept-Encoding | 客户端支持的编码压缩方式,包括gzip、deflate和br(Brotli)算法。 |
Accept-Language | 客户端偏好使用的语言顺序,首先是简体中文,然后是其他英文变种。 |
Sec-WebSocket-Key | 一个Base64编码的随机字符串,由客户端生成并发送给服务器,服务器需要根据这个值计算出一个回应摘要来完成WebSocket握手验证。 |
Sec-WebSocket-Extensions | 客户端提议启用WebSocket扩展 |
(3)wss数据包对比https数据包
- http对应于ws,https对应于wss
- http包有Headers、preview、Response、Cookies
- websocket包有Headers、Messages
- http包是文本类型数据。
- websocket包是二进制数据。
- http包是我们客户端去触发,我们请求一个地址返回一个数据。
- websocket包是当我们客户端连接服务器后,一直接受服务器的数据并且要对不同的服务发送来的数据做出回应。
(4)服务器如何知道我们离开了?
服务器判断客户端是否还在保持通信有很多方式,这里重点讲一个方式——心跳包(Heartbeat)
为了保持WebSocket连接的活跃并确保客户端仍然在线,客户端和服务器之间会定期交换“心跳”消息。如果服务器在一段时间内没有收到客户端发送的心跳消息,则可以认为客户端已经离线或者连接已断开。反过来,服务器也可以主动向客户端发送心跳消息,等待客户端确认回复。如果没有收到回复,则视为客户端已断开连接。
还是拿上面我和老大也聊天的例子:在WebSocket通信场景中,我(客户端)和老大爷(服务器)之间建立了持久的聊天管道。为了防止因为网络波动、设备休眠或其他原因导致的连接无声无息地断开,我和大爷商定了一项规则:每过一定时间,比如15秒,我就向老大爷发送一个简短的消息,例如“我还在线呢”。
-
客户端发送心跳包: 就像我在规定的时间间隔内走到老大爷面前说一声:“嘿,大爷,我还在。”(在这个例子中,“嘿,大爷,我还在。”就相当于心跳包的内容,它有一个专门的名字叫ACK确认字符,表示发来的数据已确认接收无误)
-
服务器响应心跳包: 老大爷听到你的问候后,通常会回复你一个确认信息,如:“收到,孩子,我知道了。”这样,你就能知道老大爷还清醒着,并且你们的沟通渠道依然畅通。
如果某次或连续几次心跳周期过后,老大爷没有回应我的问候,那么我可以推断出可能是老大爷暂时走开了(服务器故障),或者我喊的声音太小他没听见(网络问题),于是我可以采取行动,比如重新建立连接或是通知用户连接已断开。
五、参考资料
(1)如果你想了解更多有关websocket和http协议的细节,我推荐你去mdn的官网学习:
MDN中文官网https://developer.mozilla.org/zh-CN/docs/Learn
(2)如果你只是想快速入门websocket协议,那么看我这篇文章其实就够了,但除此之外菜鸟教程也是非常不错的新手必看网站:
菜鸟教程-websocket协议https://www.runoob.com/html/html5-websocket.html
(3)还有关于websocket的属性、事件和方法后续会再出一篇文章结合Python来给大家介绍哦!