0. 前言:

自从HTML5里的WebSocket出现后,彻底改变了以往Web端即时通讯技术的基础通道这个“痛点”(在此之前,开发者们不得不弄出了诸如:短轮询、长轮询、Comet、SSE等技术,可谓苦之久矣…),如今再也不用纠结到底该用“轮询”还是“Comet”技术来保证数据的实时性了。

1. 什么是WebSocket

WebSocket 协议在2008年诞生,2011年成为国际标准,所有浏览器都已经支持了。其是基于TCP的一种新的网络协议,是 HTML5 开始提供的一种在单个TCP连接上进行全双工通讯的协议,它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

WebSocket特点:

  • WebSocket可以在浏览器里使用
  • 支持双向通信
  • 使用简单
  • 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
  • 较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。
  • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
  • 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

2. 使用WebSocket

2.1 服务端

这里服务端用了ws这个库。相比大家熟悉的socket.io,ws实现更轻量,更适合学习的目的。

安装

1
npm install --save ws

创建WebSocket服务器的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

// 写一个数组,保持连接数
const array = []

// 有个新的连接创建了
wss.on('connection', function connection(ws) {

array.push(ws)

ws.on('error', console.error);

ws.on('message', function message(data) {
array.forEach(item => { item.send(data.toString()) })
});

ws.send('连接创建了');
});

2.2 浏览器

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
<body>
<ul>
</ul>
<input type="text" placeholder="请输入消息,按回车发送">

<script>
// 创建socket连接
const socket = new WebSocket('ws://localhost:8080');

// 监听socket打开
socket.addEventListener('open', () => {
console.log('连接成功');
});

// 监听服务器发来的消息
socket.addEventListener('message', (event) => {
// 把服务器发过来的消息放到页面上
newMessage(event.data);
})

// 渲染一条新消息
function newMessage(msg) {
// 获取ul,消息容器
const ul = document.querySelector('ul');
// 创建一条li标签
const li = document.createElement('li');
li.textContent = msg;
// 把li标签插入到ul列表
ul.appendChild(li);
}

// 获取输入框
const input = document.querySelector('input')
input.addEventListener('keyup', ({ key }) => {
if (key === 'Enter') {
// 给服务器发消息
socket.send(input.value);
input.value = '';
}
})
</script>
</body>

3. 为什么要使用WebSocket

WebSocket的目的就是解决网络传输中的双向通信的问题,HTTP1.1默认使用持久连接(persistent connection),在一个TCP连接上也可以传输多个Request/Response消息对,但是HTTP的基本模型还是一个Request对应一个Response。这在双向通信(客户端要向服务器传送数据,同时服务器也需要实时的向客户端传送信息,一个聊天系统就是典型的双向通信)时一般会使用这样几种解决方案:

  1. 轮询(polling),轮询就会造成对网络和通信双方的资源的浪费,且非实时。
  2. 长轮询,客户端发送一个超时时间很长的Request,服务器hold住这个连接,在有新数据到达时返回Response,相比 1,占用的网络带宽少了,其他类似。
  3. 长连接,其实有些人对长连接的概念是模糊不清的,我这里讲的其实是HTTP的长连接。如果你使用Socket来建立TCP的长连接,那么,这个长连接跟我们这里要讨论的WebSocket是一样的,实际上TCP长连接就是WebSocket的基础,但是如果是HTTP的长连接,本质上还是Request/Response消息对,仍然会造成资源的浪费、实时性不强等问题。