什么?还没了解WebSocket?

110 阅读7分钟

前言

WebSocket是一种网络通信协议,很多高级功能都需要用到它。

有些人会想,既然我们已经有了HTTP协议了,为什么还要花时间学另一种协议呢?这个协议到底有什么魔力,值得我们花时间学它。

因为 HTTP 协议有一个缺陷:通信只能由客户端发起。只能是客户端向服务器发出请求,服务器返回查询结果。做不到服务器主动向客户端推送信息。如果碰到一些需要服务端频繁给客户端推送信息的需求,我们用HTTP协议来实现就很麻烦。隔一段时间客户端就发起询问,了解服务器有没有新的信息。这种需求典型的例子就是聊天室,需要不断更新聊天室的内容。

轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。

简介

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。WebSocket 允许服务器端与客户端进行全双工(full-duplex)的通信。举例来说,HTTP 协议有点像发电子邮件,发出后必须等待对方回信;WebSocket 则是像打电话,服务器端和客户端可以同时向对方发送数据,它们之间存着一条持续打开的数据通道。

WebSocket还有其他特点,如下:

  1. 建立在 TCP 协议之上,服务器端的实现比较容易。
  2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  3. 可以发送文本,也可以发送二进制数据。
  4. 没有同源限制,客户端可以与任意服务器通信,完全可以取代 Ajax。
  5. 数据格式比较轻量,性能开销小,通信高效。
  6. 协议标识符是ws(如果加密,则为wss,对应 HTTPS 协议),服务器网址就是 URL。

WebSocket握手

浏览器发出的 WebSocket 握手请求类似于下面的样子:

GET / HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Host: example.com
Origin: null
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

上面的头信息之中,有一个 HTTP 头是Upgrade。HTTP1.1 协议规定,Upgrade字段表示将通信协议从HTTP/1.1转向该字段指定的协议。Connection字段表示浏览器通知服务器,如果可以的话,就升级到 WebSocket 协议。Origin字段用于提供请求发出的域名,供服务器验证是否许可的范围内(服务器也可以不验证)。Sec-WebSocket-Key则是用于握手协议的密钥,是 Base64 编码的16字节随机字符串。 服务器的 WebSocket 回应如下。

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Origin: null
Sec-WebSocket-Location: ws://example.com/

上面代码中,服务器同样用Connection字段通知浏览器,需要改变协议。Sec-WebSocket-Accept字段是服务器在浏览器提供的Sec-WebSocket-Key字符串后面,添加 RFC6456 标准规定的“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”字符串,然后再取 SHA-1 的哈希值。浏览器将对这个值进行验证,以证明确实是目标服务器回应了 WebSocket 请求。Sec-WebSocket-Location字段表示进行通信的 WebSocket 网址。

完成握手以后,WebSocket 协议就在 TCP 协议之上,开始传送数据。

客户端使用WebSocket的简单示例

WebSocket的使用方式很简单,如下就是客户端的简单使用示例:

const socket  = new WebSocket("ws://localhost:8080");

socket.addEventListener('open',(evt)=>{
  console.log('Connection open ...');
  socket.send('Hello WebSockets!');
})

// socket.onopen = function(evt) {
//   console.log('Connection open ...');
//   socket.send('Hello WebSockets!');
// };

socket.addEventListener('message',(evt)=>{
  console.log('Received Message: ' + evt.data);
  socket.close();
})

// socket.onmessage = function(evt) {
//   console.log('Received Message: ' + evt.data);
//   socket.close();
// };
socket.addEventListener('close',(evt)=>{
  console.log('Connection closed.');
})

// socket.onclose = function(evt) {
//   console.log('Connection closed.');
// };

socket.addEventListener('error',(evt)=>{
  console.log("WebSocket error: ", event);
})

// socket.onerror = function(evt) {
//   console.log("WebSocket error: ", event);
// };

WebSocket客户端的API

浏览器对 WebSocket 协议的处理,无非就是三件事。

  1. 创建连接和断开连接。
  2. 发送信息和接收信息。
  3. 处理一些错误信息。

WebSocket对象的构造函数WebSocket()

通过构造函数,创建一个WebSocket对象。如下:

new WebSocket(url)
new WebSocket(url, protocols)

第一个参数url是WebSocket 服务器将要响应的 URL。 第二个参数protocols是可选的,不填就是默认。可以是单个字符串协议,也可以是多个字符串组成的数组。

const websocket =  new WebSocket("ws://localhost:8080");

以上websocket对象就是刚创建的WebSocket对象,刚开始连,值不为空,一般表示已连接。

WebSocket对象的几大属性

  1. binaryType
  2. bufferedAmount
  3. extensions
  4. protocol
  5. readyState
  6. url

最主要的还是readyState属性,可以判断WebSocket连接的状态。

binaryType

WebSocket 连接接收的二进制数据的类型。 可以是blob也可以是arraybuffer

bufferedAmount

实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。

var data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
  // 发送完毕
} else {
  // 发送还没结束
}

extensions

服务器选择的扩展名。

protocol

服务器选择的子协议的名称。

readyState

WebSocket 连接的当前状态。 分别有四种状态。 具体如下表所示:

值(value)状态描述
0CONNECTINGWebSocket已经创建好,但是连接还没打开
1OPEN表示连接成功,可以通信了
2CLOSING连接正在关闭进程中
3CLOSED连接已经关闭或者是打开连接失败

url

由构造函数解析的 WebSocket 的绝对 URL。

WebSocket对象还有两个实例方法。

1. close()

socket.close();
socket.close(code);
socket.close(code, reason);

主动关闭WebSocket连接。 第一个参数是关闭WebSocket连接的关闭码。不具体说明的话,是1000. 第二个参数就是关闭连接的理由。

2. send()

实例对象的send()方法用于向服务器发送数据。

socket.send(data);

发送的数据可以是如下数据类型 string,ArrayBufferBlobTypedArrayDataView

如果连接只是刚创建,还没打开,就发送数据,会导致出现无效状态错误。 所以一定要等readyState的值为1时,才能发送数据。

WebSocket的监听事件

1. close

当WebSocket关闭连接时,触发close事件。 该事件有三个事件属性:

  1. code 关闭码
  2. reason 关闭连接理由
  3. wasClean 一个布尔值,是否已经关闭干净。

可以用以下两种方式监听:

addEventListener("close", (event) => {});

onclose = (event) => {};
socket.addEventListener("close", (event) => {
  console.log("The connection has been closed successfully.");
});
socket.onclose = (event) => {
  console.log("The connection has been closed successfully.");
};

2. error

当WebSocket发生错误时,触发error事件。 可以用以下两种方式监听:

addEventListener("error", (event) => {});

onerror = (event) => {};
// Create WebSocket connection
const socket = new WebSocket("ws://localhost:8080");

// Listen for possible errors
socket.addEventListener("error", (event) => {
  console.log("WebSocket error: ", event);
});

3. message

当WebSocket接收到服务器的信息时,触发message事件。 接收到的数据也有好几个类型,可以是文本,二进制数据。 可以用以下两种方式监听:

addEventListener("message", (event) => {});

onmessage = (event) => {};
// Create WebSocket connection.
const socket = new WebSocket("ws://localhost:8080");

// Listen for messages
socket.addEventListener("message", (event) => {
  console.log("Message from server ", event.data);
});

4. open

当WebSocket连接已经打开时,触发open事件。

可以用以下两种方式监听:

addEventListener("open", (event) => {});

onopen = (event) => {};
// Create WebSocket connection.
const socket = new WebSocket("ws://localhost:8080");

// Connection opened
socket.addEventListener("open", (event) => {
  socket.send("Hello Server!");
});

总结

以上就是关于如何在客户端使用WebSocket的详细用法。

参考链接: WebSocket