本文从网络协议,技术背景,安全和生产应用的方向,详细介绍 WebSocket 在 Node.js 中的落地实践。
大纲预览
本文介绍的内容包括以下方面:
- 网络协议进化
- Socket.IO?
- ws 模块实现
- Express 集成
- WebSocket 实例
- 消息广播
- 安全与认证
- BFF 应用
网络协议进化
HTTP 协议是前端最熟悉的网络通信协议。我们通常的打开网页,请求接口,都属于 HTTP 请求。
HTTP 请求的特点是:请求-> 响应
。客户端发起请求,服务端收到请求后进行响应,一次请求就完成了。也就是说,HTTP 请求必须由客户端发起,服务端才能被动响应。
除此之外,发起 HTTP 请求之前,还需要通过三次握手建立 TCP 连接。HTTP/1.0 的特点是,每通信一次,都要经历 “三步走” 的过程 —— TCP 连接 -> HTTP 通信 -> 断开 TCP 连接
。
这样的每一次请求都是独立的,一次请求完成连接就会断开。
HTTP1.1 对请求过程做了优化。TCP 连接建立之后,我们可以进行多次 HTTP 通信,等到一个时间段无 HTTP 请求发起 TCP 才会断开连接,这就是 HTTP/1.1 带来的长连接技术。
但是即便如此,通信方式依然是客户端发起,服务端响应,这个根本逻辑不会变。
随着应用交互的复杂,我们发现,有一些场景是必须要实时获取服务端消息的。
比如即时聊天,比如消息推送,用户并不会主动发起请求,但是当服务器有了新消息,客户端需要立刻知道并且反馈给用户。
HTTP 不支持服务端主动推送,但是这些场景又急需解决方案,于是早期出现了轮询(polling)。轮询是客户端定时向服务器发起请求,检测服务端是否有更新,如果有则返回新数据。
这种轮询方式虽然简单粗暴,但很显然有两个弊端:
- 请求消耗太大。客户端不断请求,浪费流量和服务器资源,给服务器造成压力。
- 不能保证及时。客户端需要平衡及时性和性能,请求间隔必然不能太小,因此会有延迟。
随着 HTML5 推出 WebSocket
,即时通讯场景终于迎来了根本解决方案。WebSocket
是全双工通信协议,当客户端与服务端建立连接之后,双方可以互相发送数据,这样的话就不需要客户端通过轮询这种低效的方式获取数据,服务端有新消息直接推送给客户端即可。
传统 HTTP 连接方式如下:
## 普通连接
http://localhost:80/test
## 安全连接
https://localhost:80/test
WebSocket 是另一种协议,连接方式如下:
## 普通连接
ws://localhost:80/test
## 安全连接
wss://localhost:80/test
但是 WebSocket 也不是完全脱离 HTTP 的,若要建立 WebSocket 连接,则必须要客户端主动发起一个建立连接的 HTTP 请求,连接成功之后客户端与服务端才能进行双向通信。
Socket.IO?
提起用 Node.js 实现 WebSocket,大家一定会想到一个库:Socket.IO
没错,Socket.IO 是目前 Node.js 在生产环境中开发 WebSocket 应用最好的选择。它功能强大,高性能,低延迟,并且可以一步集成到 express
框架中。
但是也许你不清楚,Socket.IO 并不是一个纯粹的 WebSocket 框架。它是将 Websocket 和轮询机制以及其它的实时通信方式封装成了通用的接口,以实现更高效的双向通信。
严格来说,Websocket 只是 Socket.IO 的一部分。
也许你会问:既然 Socket.IO 在 WebSocket 的基础上做了那么多的优化,并且非常成熟,那为什么还要搭一个原生 WebSocket 服务?
首先,Socket.IO 不能通过原生的 ws
协议连接。比如你在浏览器试图通过 ws://localhost:8080/test-socket
这种方式连接 Socket.IO 服务,是连接不上的。因为 Socket.IO 的服务端必须通过 Socket.IO 的客户端连接,不支持默认的 WebSocket 方式连接。
其次,Socket.IO 封装程度非常高,使用它可能不利于你了解 WebSocket 建立连接的原理。
因此,我们本篇就用 Node.js 中基础的 ws
模块,从头开始实现一个原生的 WebSocket 服务,并且在前端用 ws 协议直接连接,体验一把双向通信