当前位置: 首页 > 工具软件 > websocket++ > 使用案例 >

WebSocket

祝宏放
2023-12-01

为什么用WebSocket?
HTTP 协议有一个缺陷:通信只能由客户端发起。只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。

WebSocket最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

一共有 4 个事件:

  • open —— 连接已建立
  • message —— 接收到数据
  • error —— WebSocket 错误
  • close —— 连接已关闭
let socket = new WebSocket("wss://javascript.info/article/websocket/demo/hello");

socket.onopen = function(e) {
  alert("[open] Connection established");
  alert("Sending to server");
  // 发送消息
  socket.send("My name is John");
};

// 收到消息
socket.onmessage = function(event) {
  alert(`[message] Data received from server: ${event.data}`);
};

socket.onclose = function(event) {
  if (event.wasClean) {
    alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
  } else {
    // 例如服务器进程被杀死或网络中断
    // 在这种情况下,event.code 通常为 1006
    alert('[close] Connection died');
  }
};

socket.onerror = function(error) {
  alert(`[error] ${error.message}`);
};

事件顺序为:openmessageclose

建立 WebSocket
创建 WebSocket 对象

var Socket = new WebSocket(url, [protocol] );

创建后立即连接,在连接期间,浏览器(使用 header)问服务器是否支持WebSocket ,如果支持WebSocket 那么通信就以 WebSocket 协议继续进行,该协议根本不是 HTTP。

new WebSocket(“wss://javascript.info/chat”) 发出的请求的浏览器 header 示例

GET /chat
Host: javascript.info
Origin: https://javascript.info
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13
  • Origin —— 客户端页面的源,例如 https://javascript.info。WebSocket
    对象是原生支持跨源的。没有特殊的 header 或其他限制。旧的服务器无法处理WebSocket,因此不存在兼容性问题。但是 Origin header 很重要,因为它允许服务器决定是否使用 WebSocket 与该网站通信。
  • Connection: Upgrade —— 表示客户端想要更改协议。
  • Upgrade: websocket —— 请求的协议是 “websocket”。
  • Sec-WebSocket-Key —— 浏览器随机生成的安全密钥。
  • Sec-WebSocket-Version —— WebSocket 协议版本,当前为 13。

我们不能使用 XMLHttpRequest 或 fetch 来进行这种 HTTP 请求,因为不允许 JavaScript 设置这些 header。

如果服务器同意切换为 WebSocket 协议,服务器应该返回响应码 101:

101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=

这里 Sec-WebSocket-Accept 是 Sec-WebSocket-Key,是使用特殊的算法重新编码的。浏览器使用它来确保响应与请求相对应。

然后,就使用 WebSocket 协议传输数据,我们很快就会看到它的结构(“frames”)。它根本不是 HTTP。

数据传输
WebSocket 通信由 “frames”(即数据片段)组成,可以从任何一方发送,并且有以下几种类型:

  • “text frames” —— 包含各方发送给彼此的文本数据。
  • “binary data frames” —— 包含各方发送给彼此的二进制数据。
  • “ping/pong frames” 被用于检查从服务器发送的连接,浏览器会自动响应它们。
  • 还有 “connection close frame” 以及其他服务 frames。

在浏览器里,我们仅直接使用文本或二进制 frames。

WebSocket .send() 方法可以发送文本或二进制数据。

socket.send(body) 调用允许 body 是字符串或二进制格式,包括 BlobArrayBuffer 等。不需要额外的设置:直接发送它们就可以了。

当我们收到数据时,文本总是以字符串形式呈现。而对于二进制数据,我们可以在 Blob 和 ArrayBuffer 格式之间进行选择。

它是由 socket.binaryType 属性设置的,默认为 “blob”,因此二进制数据通常以 Blob 对象呈现。

Blob 是高级的二进制对象,它直接与 < a >,< img > 及其他标签集成在一起,因此,默认以 Blob 格式是一个明智的选择。但是对于二进制处理,要访问单个数据字节,我们可以将其改为 “arraybuffer”:

socket.binaryType = "arraybuffer";
socket.onmessage = (event) => {
  // event.data 可以是文本(如果是文本),也可以是 arraybuffer(如果是二进制数据)
};

限速
反复地调用 **socket.send(data)*数据将会缓冲(储存)在内存中,并且只能在网速允许的情况下尽快将数据发送出去。

socket.bufferedAmount 属性储存目前已缓冲的字节数,等待通过网络发送。

我们可以检查它以查看 socket 是否真的可用于传输。

// 每 100ms 检查一次 socket
// 仅当所有现有的数据都已被发送出去时,再发送更多数据
setInterval(() => {
  if (socket.bufferedAmount == 0) {
    socket.send(moreData());
  }
}, 100);

连接关闭

socket.close([code], [reason]);
  • code 是一个特殊的 WebSocket 关闭码(可选)
  • reason 是一个描述关闭原因的字符串(可选)

通过 close 事件处理器获取了关闭码和关闭原因,例如:

// 关闭方:
socket.close(1000, "Work complete");

// 另一方
socket.onclose = event => {
  // event.code === 1000
  // event.reason === "Work complete"
  // event.wasClean === true (clean close)
};

最常见的数字码:

  • 1000 —— 默认,正常关闭(如果没有指明 code 时使用它),
  • 1006 —— 没有办法手动设定这个数字码,表示连接丢失(没有 close frame)。

还有其他数字码,例如:

  • 1001 —— 一方正在离开,例如服务器正在关闭,或者浏览器离开了该页面,
  • 1009 —— 消息太大,无法处理,
  • 1011 —— 服务器上发生意外错误,

连接状态
要获取连接状态,可以通过带有值的 socket.readyState 属性:

  • 0 —— “CONNECTING”:连接还未建立,
  • 1 —— “OPEN”:通信中,
  • 2 —— “CLOSING”:连接关闭中,
  • 3 —— “CLOSED”:连接已关闭。

总结
WebSocket 是一种在浏览器和服务器之间建立持久连接的现代方式。

  • WebSocket 没有跨源限制。
  • 浏览器对 WebSocket 支持很好。
  • 可以发送/接收字符串和二进制数据。
 类似资料: