HTTP,WebSocket 和 聊天室

熊的猫熊的猫 -
HTTP,WebSocket 和 聊天室
前言

在涉及到网络层面的相关内容时,不免会联系到 HTTP、TCP、WebSocket 等,但相信大部分人都并不是很清楚其中的一些关系和概念,特别是需要你去做语言表述时,网上有不少优秀的资料和文章,但知识仍需要自己去消化和总结,于是有了本文!!!

3DED65F8.gif

本文的核心内容就是 WebSocket,主要从以下几个方面来进行介绍和实践:

是什么 WebSocketwhat)为什么需要 WebSocketwhyWebSocketHTTP 的关系WebSocket 的使用场景 (when / whereWebSocket 实现简易聊天室(how)吾乃 WebSocket

为了更好的解释 WebSocket 是什么,就需要一个熟悉且常用的东西来作为对比参考,它就是 HTTP。

HTTP

在大多数项目都会使用 HTTP 协议来实现前后端交互,而 HTTP 协议又是基于 TCP 协议来建立连接的,它们的关系是大致如下:

image.png

全双工 & 半双工

所谓 全双工 指的是发送端和接收端是可以 随时(包含同一时刻) 向对方发送消息进行通信,而 半双工 指的是发送端和接收端是也可以向对方发送消息进行通信,但是 同一时刻 只能有一方进行发送动作。

image.png

我们常用 HTTP1.1 协议就属于 半双工,即服务端不具备主动推送数据资源给客户端的能力,当服务端需要推送数据给客户端时,必须要客户端先发起一个请求,这也被称为 请求-响应 模型。

长轮询 & 服务端推送

由于 HTTP1.1 协议并不支持服务端主动向客户端发送数据消息,但实际需求又有需要实现这样的功能,比如扫码登录:

image.png

基于 请求-响应 模型如果我们需要服务端的消息数据,就必须先向服务端发送对应的查询请求,因此只要每隔一段时间向服务器发起查询请求,在根据响应结果决定是继续下一步操作,还是继续发起查询。

但这个查询请求是需要设定时间间隔,而时间间隔可结合 HTTP 请求超时时间来得出,毕竟如果一直发送查询请求,就会得到很多无意义的查询请求和响应结果。

上述在用户无感知下实现 服务端推送 功能的方案,其实就是所谓的 长轮询

WebSocketHTTP & 超文本

前面我们知道了 HTTP1.1 是 半双工,而 HTTP 是基于 TCP 的,然后 TCP 是 全双工 的,也就是说 HTTP1.1 中根本没用到 TCP 的 全双工 的能力,这是为啥?

早期 HTTP(超文本传输协议) 主要目的就是传输超文本,因为当时网络上绝大多数的资源都是纯文本,许多通信协议也都使用纯文本,因此 HTTP 在设计上不可避免地受到了时代的限制。

基于 TCP 的新协议

由于 HTTP 存在早期设计上的限制,因此需要一种新的基于 TCP 实现 全双工通信 的协议,而这个协议就是 WebSocket

image.png

WebSocket 其中的 Socket 其实并不是一个协议,它是为了方便使用 TCP 或 UDP 而抽象出来的一层,是位于应用层和传输控制层之间的一组接口.建立连接

从上述内容可以知道,HTTP 和 WebSocket 都是基于 TCP 的,因此建立连接的过程肯定少不了大家熟知的 TCP 三次握手,关于这部分在早前的 重新认识 TCP 三次握⼿和四次挥⼿ 一文中有过相关的介绍,这里不过多阐述。

image.png

接下来我们从下面的几个问题来一窥究竟!!!

服务端如何知道本次的协议是 WebSocket 呢?

image.png

答案很简单,就是通过 请求头 来做标识:

Connection: Upgrade

Connection 特定于连接的标头字段,最常见的指定值包含 "close、keep-alive" 等,它不能与 HTTP/2 一起使用

Upgrade: websocket

HTTP 1.1 中用 Upgrade 标头可用于将已建立的客户端/服务器连接升级到不同的协议(通过相同的传输协议)例如,客户端可以使用它来将连接从 HTTP 1.1 升级到 HTTP 2.0,或者将 HTTPHTTPS 连接升级到 WebSocket而在 HTTP/2 中是被明确禁止使用这种机制/标头

Sec-WebSocket-Key

是由浏览器随机生成的一个 Base64 编码字符串值

Sec-WebSocket-Version

表明客户端所使用的协议版本

Sec-WebSocket-Extensions

表示客户端想要表达的协议级的扩展

服务端响应 101 状态码表示什么?

image.png

客户端发送了对应的关于将协议升级的信息 Upgrade: websocket,但并不会立马就会建立连接,因为服务端必须也要支持 websocket 协议,否则就会失败

如果服务端支持 websocket 协议,那么服务端就会返回状态码为 101 且带有响应头 Sec-WebSocket-Accept 的消息给客户端

Status Code: 101 表示指服务器将按照请求头信息变为一个不同的协议,是 HTTP 1.1 中新加入的Sec-WebSocket-Accept 中存放的是在请求头 Sec-WebSocket-Key 中经过特定加密算法得到的结果

什么时候连接建立完成?

其实在前两个问题中就已经完成 WebSocket 的连接了,也就是 两次握手

客户端发送携带 Connection: Upgrade、Upgrade: websocket、Sec-WebSocket-Key: VE9X2xPp8teUvHEvmBL8Tw== 等必要信息请求头的请求给服务端服务端若支持对应的协议升级,则会响应客户端,并返回状态码为 101 且响应头携带 Sec-WebSocket-Accept 值为 加密算法 处理 Sec-WebSocket-Key 后的值经过两次 HTTP 握手后,后续的通信就由 websocket 协议接手

image.png

WebSocket 使用场景

WebSocket 的特性非常适用于 即时性高 的服务场景,例如:

视频弹幕类协同编辑类媒体聊天类多玩家游戏类实时监控类(如 运动轨迹)扫码登录......

相信这些大家都没有什么疑问,但值得思考的是,在大多数的项目里也未必会真正的使用到 WebSocket,这一点是值得思考的。

为什么大多可以用 WebSocket 的场景,却没有真正使用呢?

打开网页上随处可见的客服聊天窗口,结合上述的介绍,也许你就会认为是通过 WebSocket 实现的,但其实不然,例如:

image.png

你会发现这样的场景下,仍然没有使用 WebSocket ,其实多是因为历史项目的兼容性问题,WebSocket 的特性虽好用,但 WebSocket 仍然不是 HTTP

如果之前系统链路是针对 HTTP 协议设计的,那么改造带来的中间风险与特殊处理的地方是否能把控好?这样的改造成本与收益是否合理?

因此,大多数场景下还是会使用:短连接(心跳包测试)+ 长链接(接收消息) 的方式来实现。

服务端推送并非 WebSocket 不可

WebSocket 看起来真香,但并不是所有场景都可以直接使用,在只需要 服务端推送 的场景下,也并非没有其他方式可以选择,如 EventSourceServer Push 就可实现。

EventSource — SSE(Server-Sent-Events)

EventSource 是服务器推送的一个网络事件接口。一个 EventSource 实例会对 HTTP 服务开启一个 持久化 的连接,以 text/event-stream 格式发送事件,会一直保持开启直到被要求关闭。

换句话说,EventSource 基于 HTTP 协议的 单向通信,即数据信息被单向从 服务端客户端 分发,当不需要以消息形式将数据从 客户端 发送到 服务器 时,EventSource 无疑是一个有效方案。

这里不在讲具体用法,具体用法可 参考此处!优点SSE 基于 HTTP 的轻量级协议,改造成本不大SSE 默认支持 断线重连SSE 支持发送 自定义数据类型缺点SSE 不支持 CORS,参数 url 表示服务器网址,且 url协议、域名、端口 需要与当前页面的网址保持一致

兼容性较差,只适用高级浏览器,不支持 IE 浏览器

可通过 event-source-polyfill 进行 IE 的兼容处理只支持 单向通信,即只能服务器向客户端推送数据,客户端无法向服务器端推送数据HTTP/2 (Server Push)

HTTP 的每个版本都是基于之前的版本来试下优化的,就像 HTTP/1.1 是基于 HTTP/1.0 进行的优化,同样的 HTTP/2 也是基于 HTTP/1.1 进行的优化。

为了解决 HTTP/1.1 链接需要请求以正确的顺序发送,理论上可用一些并行的链接(58 个)所带来的成本和复杂性,在 2010 年早期,谷歌通过实践了一个实验性的 SPDY 协议,随后明确了响应数量的增加和解决复杂的数据传输,SPDY 成为了 HTTP/2 协议的基础。

HTTP/2 相比于 HTTP1.1 的特性HTTP/2二进制协议 而非 HTTP1.1文本协议HTTP/2 支持 多路复用,即并行请求可在 同一个 TCP 连接 中处理HTTP/2 采用 HPACK 算法实现 头部压缩,在客户端和服务器间建立 “字典”,用索引号表示重复的字符串,并采用哈夫曼编码来压缩整数和字符串HTTP/2 提供 Server Push服务端推送 的能力实现提前请求

而其中 Server Push 服务端推送缺点 就是只能向客户端推送 静态资源,而不能推送 自定义数据

所谓的 Server Push 这里举个例子就很容易理解了:

HTTP/2 之前访问一个站点:

服务器返回对应的 xxx.html 文件客户端预解析 xxx.html 文件根据预解析的识别到的 link、script 标签等并行加载文件资源...

HTTP/2 后访问一个站点:

服务器返回对应的 xxx.html 文件,同时可以返回相应的 x.css、x.js 等资源,即实现了静态资源的 提前请求,于是就能加快页面的渲染和显示Chrome 将移除对 Server Push 支持

稍微总结一下,Server Push 会在响应 HTML 文件时,服务器 会同时将所需的资源文件 主动推送 给浏览器,并将资源会缓存到本地,当解析 HTML 时所需加载的资源会直接从本地缓存中读取,不需要再额外等待网络传输。

看起来似乎没什么大问题,但却有着比较大的 缺陷

Server Push 难以避免的会推送浏览器已经拥有的 子资源,因为很多资源会在浏览器 首次请求 被响应时就已缓存下来这样的 过度推动/无意义推送 会导致 网络带宽使用效率降低,因此会明显降低整体的性能优势

总体而言,Chrome 数据显示 HTTP2/Push 实际上对整个网络的性能产生了 负面影响,因此 Chrome 宣布将在下一个主要版本(Chrome 106)中将删除对 Server Push 的支持,点此了解更多

WebSocket 实现聊天室

上面说了那么多,终究还是得落实到代码上!

543B41AA.png543B41AA.png543B41AA.png

实现功能

以下实现的都是以极简的方式实现的,很多东西不会考虑的很全面,例如数据存储方面完全没有用到数据库,大家感兴趣可以自己完善:

用户注册

昵称标识生成用户 uuid

入群欢迎提示

根据 uuid 判断用户是否首次加入群聊,若是则进行欢迎提示

群聊消息收发

当前用户发送消息时,通知服务端将聊天信息存储在服务端的 chatList 中当 chatList 列表数据发生变化,通过广播的方式发送给其他连接的用户

群消息同步

用户每次进入聊天室,会同步接收其他用户之前的聊天信息

.gif 表情包

表情包数据存储在 emotions.json 文件中用户选择表情后会作为对应的字符添加到消息中,当该消息同步给其他用户时,会根据 emotions.json 的数据做匹配和生成

头像切换

当前用户可通过点击自己的头像选择对应的图片替换默认头像因为不涉及图片存储,因此通过 FileReader 将接收到的 File 文件转成 base64 的格式效果演示

image.png

源码

戳此可直达源码位置:源码地址

源码中通过 socket.iosocket.io-client 来实现,主要是因为 socket.io 则已经做了很多基础性工作,能够很好地与一些主流的技术集成,开发者只需要完成一些简单的配置即可。

前端部分

可通过 npm run dev 启动

服务端部分

可通过 npm run server 启动

局域网访问

若要支持局域网访问,则需要在 vite.config.ts 指定 host 配置选项,对应的 IP 地址可通过 ipconfigCMD 终端上查看若仍不支持局域网访问,可尝试短暂关闭防火墙后在访问参考为什么有HTTP协议,还要有websocket协议?数据推送解决方案之eventSource实战应用Node HTTP/2 Server Push 从了解到放弃developer.mozilla.org
特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

Tags 标签

前端httpvue.jsjavascriptnode.js

扩展阅读

加个好友,技术交流

1628738909466805.jpg