JavaScript内核与高级编程之:`WebSockets`:其在`JavaScript`中的实现与`Subprotocol`。

嘿,各位!今天咱们聊聊 WebSockets,这玩意儿可是让你的 Web 应用瞬间“活”起来的秘密武器!

别听到“内核”、“高级编程”就害怕,其实 WebSocket 就像是你和服务器之间建立了一条“私人专线”,从此以后,你们之间就能像老朋友一样,随时随地、你一言我一语地聊天,再也不用像以前那样,每次都得客客气气地发个请求,然后眼巴巴地等着服务器回话了。

啥是 WebSockets?

简单来说,WebSocket 是一种网络通信协议,它在客户端和服务器之间建立一个持久的、双向的连接。这意味着一旦连接建立,双方都可以随时发送数据,而不需要每次都重新建立连接。想想以前的 HTTP,那可是典型的“一次性买卖”,你发个请求,服务器给你个响应,交易就结束了,下次再想聊天,还得重新来一遍。WebSocket 就牛逼多了,它让你的 Web 应用拥有了“实时”能力,就像微信聊天一样,这边刚发出去,那边立刻就能收到。

为什么需要 WebSockets?

在 WebSocket 出现之前,要实现实时通信,开发者们可是绞尽脑汁,各种奇技淫巧都用上了,比如:

  • 轮询 (Polling): 客户端定时向服务器发送请求,询问是否有新的数据。 就像一个好奇宝宝,每隔几秒钟就问服务器:“有新消息吗?有新消息吗?” 服务器烦都烦死了,而且大部分时间都是在回答:“没有!” 浪费资源不说,还延迟高。
  • 长轮询 (Long Polling): 客户端发送请求后,服务器不会立即返回响应,而是会一直等待,直到有新的数据才返回。 如果一直没有新数据,服务器会等待一段时间后返回一个空的响应,然后客户端再重新发起请求。 比轮询好一点,但依然不够优雅。
  • 服务器发送事件 (Server-Sent Events, SSE): 服务器可以主动向客户端推送数据,但客户端只能被动接收,不能主动发送。 就像一个广播电台,只能单方面地播放节目。

这些方法虽然也能实现一定程度的实时通信,但都有各自的缺点,比如延迟高、资源浪费、只能单向通信等等。 而 WebSocket 的出现,完美地解决了这些问题。

WebSockets 的工作原理

WebSocket 基于 TCP 协议,并且使用了 HTTP 协议的握手过程。 这意味着它能够穿透大多数防火墙和代理服务器,并且能够与现有的 Web 基础设施很好地集成。

握手 (Handshake)

当客户端想要与服务器建立 WebSocket 连接时,它会发送一个 HTTP Upgrade 请求。 这个请求告诉服务器:“嘿,我想升级到 WebSocket 协议,咱们以后就用 WebSocket 协议聊天吧!”

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
  • Upgrade: websocket: 表示客户端想要升级到 WebSocket 协议。
  • Connection: Upgrade: 表示这是一个升级请求。
  • Sec-WebSocket-Key: 一个随机的 Base64 编码的字符串,用于安全验证。
  • Sec-WebSocket-Version: WebSocket 协议的版本。

如果服务器支持 WebSocket 协议,它会返回一个 HTTP 101 Switching Protocols 响应。 这个响应告诉客户端:“没问题,咱们以后就用 WebSocket 协议聊天吧!”

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  • Sec-WebSocket-Accept: 服务器根据客户端发送的 Sec-WebSocket-Key 计算出来的,用于验证服务器是否支持 WebSocket 协议。

一旦握手成功,客户端和服务器之间就建立了一个持久的 WebSocket 连接。 双方就可以随时发送数据,而不需要每次都重新建立连接。

数据传输

WebSocket 连接建立后,客户端和服务器之间就可以通过 帧 (Frame) 来发送数据。 每个帧都包含一些元数据,比如帧的类型、数据的长度等等。 WebSocket 协议定义了多种帧类型,比如文本帧、二进制帧、控制帧等等。

JavaScript 中的 WebSockets

在 JavaScript 中,可以使用 WebSocket 对象来创建和管理 WebSocket 连接。

创建 WebSocket 连接

const socket = new WebSocket('ws://example.com/chat');  // 注意:协议是 ws 或 wss (安全连接)
  • ws:// 用于非加密的 WebSocket 连接。
  • wss:// 用于加密的 WebSocket 连接 (推荐使用)。

WebSocket 事件

WebSocket 对象提供了一些事件,用于监听 WebSocket 连接的状态变化和接收数据。

  • open: 当 WebSocket 连接建立成功时触发。

    socket.addEventListener('open', (event) => {
      console.log('WebSocket 连接已建立!');
      // 就可以发送数据了
      socket.send('你好,服务器!');
    });
  • message: 当收到服务器发送的数据时触发。

    socket.addEventListener('message', (event) => {
      console.log('收到消息:', event.data);
    });
  • close: 当 WebSocket 连接关闭时触发。

    socket.addEventListener('close', (event) => {
      console.log('WebSocket 连接已关闭!');
      console.log('关闭码:', event.code);
      console.log('关闭原因:', event.reason);
    });
  • error: 当发生错误时触发。

    socket.addEventListener('error', (event) => {
      console.error('WebSocket 发生错误:', event);
    });

发送数据

可以使用 send() 方法向服务器发送数据。

socket.send('Hello, server!');  // 发送文本数据
socket.send(new Uint8Array([0x01, 0x02, 0x03]));  // 发送二进制数据

关闭连接

可以使用 close() 方法关闭 WebSocket 连接。

socket.close();  // 关闭连接
socket.close(1000, '正常关闭');  // 带关闭码和关闭原因

Subprotocol:让 WebSocket 更专业

虽然 WebSocket 协议本身已经很强大了,但它只提供了一个通用的数据传输通道。 如果你想在 WebSocket 上实现更复杂的应用,比如聊天应用、游戏应用等等,就需要定义自己的数据格式和协议。 这就是 Subprotocol 的作用。

Subprotocol 允许你在 WebSocket 连接上使用自定义的协议。 你可以通过在握手阶段指定 Sec-WebSocket-Protocol 头来告诉服务器你想使用哪个 Subprotocol。

为什么需要 Subprotocol?

想象一下,如果没有 Subprotocol,你的 WebSocket 连接就像一个没有标签的快递箱,里面装了什么东西,服务器根本不知道。 服务器只能把所有的数据都当成纯文本来处理,然后开发者自己再去解析这些数据,非常麻烦。

有了 Subprotocol,你的 WebSocket 连接就变成了一个贴了标签的快递箱,服务器一眼就能知道里面装的是什么东西,然后就可以按照相应的协议来处理这些数据。

如何使用 Subprotocol?

  1. 客户端在握手阶段指定 Sec-WebSocket-Protocol 头。

    const socket = new WebSocket('ws://example.com/chat', ['chat', 'json']);

    这里指定了两个 Subprotocol:chatjson。 服务器会选择其中一个它支持的 Subprotocol,并在握手响应中返回。

  2. 服务器在握手响应中返回 Sec-WebSocket-Protocol 头。

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: ...
    Sec-WebSocket-Protocol: chat

    这里服务器选择了 chat 这个 Subprotocol。

  3. 客户端和服务器在连接建立后,就可以按照指定的 Subprotocol 来发送和接收数据了。

常用的 Subprotocol

  • JSON: 使用 JSON 格式来传输数据。 这是一种非常常见和方便的 Subprotocol,特别适合于传输结构化的数据。
  • Protocol Buffers (protobuf): 一种高效的二进制数据序列化格式。 适合于传输大量数据,并且对性能要求较高的应用。
  • MessagePack: 另一种高效的二进制数据序列化格式。 类似于 JSON,但更加紧凑和快速。
  • STOMP (Simple Text Oriented Messaging Protocol): 一种简单的文本消息协议。 适合于构建消息队列系统。

自定义 Subprotocol

如果你觉得现有的 Subprotocol 都不能满足你的需求,你也可以自定义 Subprotocol。 自定义 Subprotocol 只需要定义好数据格式和协议规则,然后在客户端和服务器端按照这些规则来发送和接收数据就可以了。

举个例子:

假设我们要创建一个简单的聊天应用,我们可以定义一个名为 chat 的 Subprotocol,使用以下 JSON 格式来传输消息:

{
  "type": "message",
  "from": "Alice",
  "to": "Bob",
  "content": "Hello, Bob!"
}

客户端和服务器端都需要按照这个格式来发送和接收消息。

客户端代码:

const socket = new WebSocket('ws://example.com/chat', ['chat']);

socket.addEventListener('open', (event) => {
  console.log('WebSocket 连接已建立!');

  const message = {
    type: 'message',
    from: 'Alice',
    to: 'Bob',
    content: 'Hello, Bob!'
  };

  socket.send(JSON.stringify(message));
});

socket.addEventListener('message', (event) => {
  const message = JSON.parse(event.data);

  if (message.type === 'message') {
    console.log(`${message.from} 说:${message.content}`);
  }
});

服务器端代码 (Node.js 使用 ws 库):

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws, req) => {
  console.log('客户端已连接!');

  // 检查客户端是否选择了 chat Subprotocol
  if (req.headers['sec-websocket-protocol'] !== 'chat') {
    ws.close(1002, '不支持的 Subprotocol'); // 1002: Protocol Error
    return;
  }

  ws.on('message', (message) => {
    try {
      const parsedMessage = JSON.parse(message);

      if (parsedMessage.type === 'message') {
        console.log(`${parsedMessage.from} 说:${parsedMessage.content}`);

        // 将消息转发给其他客户端 (这里只是一个简单的示例,实际应用需要更复杂的逻辑)
        wss.clients.forEach((client) => {
          if (client !== ws && client.readyState === WebSocket.OPEN) {
            client.send(message);
          }
        });
      }
    } catch (error) {
      console.error('解析消息失败:', error);
      ws.send(JSON.stringify({ type: 'error', message: 'Invalid message format' }));
    }
  });

  ws.on('close', () => {
    console.log('客户端已断开连接!');
  });
});

console.log('WebSocket 服务器已启动,监听端口 8080');

这个简单的例子展示了如何使用自定义 Subprotocol 来实现一个基本的聊天功能。 实际应用中,你可以根据自己的需求来定义更复杂的数据格式和协议规则。

WebSockets 的优缺点

优点:

  • 实时性: WebSocket 提供了真正的实时通信能力,延迟非常低。
  • 双向通信: 客户端和服务器可以随时发送数据,不需要每次都重新建立连接。
  • 效率: WebSocket 使用二进制帧来传输数据,比 HTTP 更加高效。
  • 标准化: WebSocket 是一种标准的 Web 协议,得到了广泛的支持。

缺点:

  • 复杂性: 相对于 HTTP 来说,WebSocket 的实现和管理更加复杂。
  • 状态维护: WebSocket 连接是持久的,需要服务器端维护连接状态。
  • 安全性: 需要注意 WebSocket 连接的安全性,防止恶意攻击。

WebSockets 的应用场景

  • 聊天应用: 这是 WebSocket 最常见的应用场景之一。
  • 在线游戏: WebSocket 可以提供低延迟的实时通信,非常适合于在线游戏。
  • 实时数据更新: 比如股票行情、体育赛事直播等等。
  • 物联网 (IoT): WebSocket 可以用于连接各种 IoT 设备,实现远程控制和数据采集。
  • 协作工具: 比如在线文档编辑、代码协同等等。

总结

WebSocket 是一种非常强大的 Web 技术,它可以让你的 Web 应用拥有真正的实时能力。 通过 Subprotocol,你可以定义自己的数据格式和协议,让 WebSocket 更加专业和灵活。 虽然 WebSocket 的实现和管理可能比较复杂,但它的优点是显而易见的,值得你花时间去学习和掌握。

好了,今天的 WebSockets 讲座就到这里。 希望大家能够学有所获,并且能够将 WebSocket 应用到自己的项目中。 如果有什么问题,欢迎随时提问!咱们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注