WebSocket(二)

一、引言

在了解 WebSocket 的基本概念与工作原理后,深入探究其数据帧与控制帧结构,对于理解 WebSocket 如何高效传输数据、维持连接稳定至关重要。本文将详细剖析 WebSocket 的数据帧与控制帧,并结合代码示例,让你对其内部机制有更清晰的认识。

二、WebSocket 数据帧结构

2.1 帧头剖析

  1. fin 标志位
    • fin 占 1 位,用于标识当前帧是否为一个完整消息的最后一帧。当 fin = 1 时,表示这是消息的最后一部分;fin = 0 则意味着后续还有更多帧构成完整消息。例如,在传输大文件时,文件数据可能被分割成多个帧,只有最后一帧的 fin 为 1。
  2. 保留位 rsv1、rsv2、rsv3
    • 这三个标志位各占 1 位,目前主要为未来协议扩展保留,正常情况下设为 0。若在特定场景下使用扩展功能,可通过设置这些位来启用。
  3. 操作码 opcode
    • opcode 占 4 位,决定了帧的类型和用途。常见的 opcode 值及其含义如下:
      • 0x0:连续帧(Continuation Frame),用于当一个消息被拆分成多个帧时,除第一帧外的后续帧。例如,一个长文本消息的第二帧及之后的帧可能是连续帧,它们与第一帧共同构成完整消息。
      • 0x1:文本帧(Text Frame),用于传输文本数据,数据以 UTF - 8 编码。如即时通讯中的文本消息,通常以文本帧形式传输。
      • 0x2:二进制帧(Binary Frame),用于传输二进制数据,如图片、音频、视频等。在实时视频流传输中,视频数据可能以二进制帧发送。
      • 0x8:关闭帧(Close Frame),用于关闭 WebSocket 连接,包含关闭状态码和可选的关闭原因。
      • 0x9:Ping 帧,用于发送心跳检测请求,接收方需回复 Pong 帧。
      • 0xA:Pong 帧,作为对 Ping 帧的响应,用于确认连接正常。
  4. 掩码位 masked
    • masked 占 1 位,在客户端向服务器发送的帧中,此位必须为 1。掩码是一个 32 位的随机数,用于对有效载荷数据进行混淆处理,以增强安全性。服务器接收数据时,需根据掩码位和掩码数据还原真实数据。
  5. 有效载荷长度 payload_length
    • payload_length 用于表示有效载荷数据的长度,其长度编码较为灵活:
      • 若值小于 126,该 7 位直接表示有效载荷的长度。
      • 若值为 126,后续 2 个字节表示有效载荷长度。
      • 若值为 127,后续 8 个字节表示有效载荷长度。这种设计可适应不同大小的数据传输需求,大文件传输时能准确表示数据长度。

2.2 有效载荷

有效载荷是帧中实际传输的数据部分。根据 opcode 类型不同,有效载荷的数据格式和含义各异。例如,文本帧的有效载荷是 UTF - 8 编码的文本;二进制帧的有效载荷是二进制数据。

三、WebSocket 控制帧结构

3.1 Ping 帧

  1. 作用
    Ping 帧主要用于检测客户端和服务器之间的连接状态,确保连接处于活跃状态。发送方定期发送 Ping 帧,接收方必须及时回复 Pong 帧。
  2. 结构
    Ping 帧的 opcode 为 0x9,通常只包含帧头,有效载荷部分可包含任意数据,一般用于携带一些诊断或调试信息。例如,可在 Ping 帧有效载荷中包含当前时间戳,接收方回复 Pong 帧时带回该时间戳,发送方据此计算往返时间,检测网络延迟。

3.2 Pong 帧

  1. 作用
    Pong 帧作为对 Ping 帧的响应,确认接收方已收到 Ping 帧,表明连接正常。若发送方在一定时间内未收到 Pong 帧,可认为连接出现问题,进行相应处理,如重新建立连接。
  2. 结构
    Pong 帧的 opcode 为 0xA,同样可包含帧头和有效载荷。有效载荷数据通常与对应的 Ping 帧有效载荷数据相同,用于反馈和确认。

3.3 Close 帧

  1. 作用
    Close 帧用于有序关闭客户端和服务器之间的 WebSocket 连接。它允许双方在关闭连接时传递状态码和关闭原因,使对方了解关闭的原因。
  2. 结构
    Close 帧的 opcode 为 0x8,有效载荷部分可包含 2 字节的状态码和可选的 UTF - 8 编码的关闭原因字符串。常见状态码如 1000 表示正常关闭,1001 表示终端正在离开,1008 表示违反协议等。

四、代码示例:自定义构建与解析 WebSocket 帧

4.1 构建发送帧(JavaScript)

以下代码展示如何在 JavaScript 中构建一个简单的文本帧:

function createTextFrame(message) {
    const encoder = new TextEncoder();
    const data = encoder.encode(message);
    const length = data.length;
    let frame = new Uint8Array(length + 2);
    // 设置 fin 为 1,opcode 为 0x1(文本帧)
    frame[0] = 0x81;
    if (length < 126) {
        frame[1] = length;
    } else if (length < 65536) {
        frame[1] = 126;
        frame.set(new Uint16Array([length]), 2);
    } else {
        frame[1] = 127;
        frame.set(new BigUint64Array([length]), 2);
    }
    frame.set(data, length < 126? 2 : length < 65536? 4 : 10);
    return frame;
}

// 使用示例
const message = "Hello, WebSocket!";
const frame = createTextFrame(message);
// 这里假设已有 WebSocket 连接对象 socket
// socket.send(frame);

4.2 解析接收帧(JavaScript)

function parseFrame(buffer) {
    const view = new DataView(buffer);
    const fin = (view.getUint8(0) & 0x80) === 0x80;
    const opcode = view.getUint8(0) & 0x0F;
    const masked = (view.getUint8(1) & 0x80) === 0x80;
    let payloadLength = view.getUint8(1) & 0x7F;
    let offset = 2;
    if (payloadLength === 126) {
        payloadLength = view.getUint16(offset, true);
        offset += 2;
    } else if (payloadLength === 127) {
        payloadLength = view.getBigUint64(offset, true);
        offset += 8;
    }
    let mask;
    if (masked) {
        mask = view.getUint32(offset, true);
        offset += 4;
    }
    const payload = new Uint8Array(payloadLength);
    for (let i = 0; i < payloadLength; i++) {
        let byte = view.getUint8(offset + i);
        if (masked) {
            byte ^= (mask >> ((i % 4) * 8)) & 0xFF;
        }
        payload[i] = byte;
    }
    return { fin, opcode, payload };
}

// 使用示例,假设已接收到数据帧 buffer
const parsedFrame = parseFrame(buffer);
console.log('解析结果:', parsedFrame);

五、总结

深入理解 WebSocket 的数据帧与控制帧结构,能帮助开发者更好地优化 WebSocket 通信,处理复杂场景,提升应用性能与稳定性。通过本文的代码示例,希望你能在实际项目中灵活运用这些知识,构建出更强大的实时通信应用。

### WebSocket 进制数据传输 #### 发送和接收进制数据的方法 为了在 WebSocket 中发送进制数据,通常会使用 `ArrayBuffer` 或者 `Blob` 对象作为消息体。当创建 WebSocket 连接实例时,默认情况下该对象只接受字符串形式的消息;但是可以通过设置属性 `binaryType` 来改变这一行为以便支持进制格式的数据交换。 对于浏览器环境中的 JavaScript 应用程序来说,一旦建立了 WebSocket 连接并准备好了要传送的信息之后就可以调用 send 方法来发送这些信息了[^1]: ```javascript // 创建一个新的WebSocket连接 const socket = new WebSocket('ws://example.com/socket'); // 设置 binaryType 属性为 'arraybuffer' 表明我们将处理 ArrayBuffer 类型的数据 socket.binaryType = "arraybuffer"; let array = new Uint8Array([72, 101, 108, 108, 111]); // Hello in ASCII code points // 当连接打开时触发此事件处理器并向服务器发送一条消息 socket.addEventListener('open', function (event) { socket.send(array.buffer); }); ``` 上述代码片段展示了如何配置一个 WebSocket 实例以允许其发送进制数据,并且还提供了一个简单的例子说明怎样构建和发送这样的数据包给远程服务端[^3]。 另外值得注意的是,在 HTTPS 页面上应该始终采用 WSS 协议来进行加密通信,因为现代浏览器不允许混合内容加载(即在一个安全页面内发起不安全请求),这同样适用于 WebSockets 的 URL 方案选择——HTTPS 路径下的网页无法建立 WS 非加密连接而必须使用 WSS 加密连接[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值