自定义WebSocKet的实现

应用层的网络协议,本质是就是两大块,协议体的定义和网络编程的模型,网络编程模型现在主流就是NIO和AIO但是大多数还是以Netty的优化过的NIO为主,所以实际上很多协议的出现比如rpc,http2它们为了快,大多都是选择如何降低传输的桢的大小,比如http2的分片思想,因为桢小,传输的才更快。但http3.0做了个离谱的操作,用UDP代替TCP来以求更快,稳定性问题通过中间再引入一个中间件也就是所谓的QUIC,思想就是客户端到中间件用UDP,中间件到服务端用UDP或者TCP。反正架构问题,解决不了就中间引入一个中间件,大多都能解决。


高性能NIO通信框架:自定义WebSocket协议设计与实现

在高并发、低延迟的网络通信框架中,应用层协议的设计和实现对系统的整体性能至关重要。本文将介绍一个基于自定义协议的WebSocket替代方案,讨论协议体的设计、消息字段定义、分片机制及并发支持等方面,并结合代码实例展示如何实现一个高效的协议解析和分片重组工具。

一、协议体设计

在我们的自定义WebSocket协议中,协议体的设计需要考虑到高效解析和分片重组。协议体的设计包括多个关键字段,确保消息在传输过程中能够被正确解析和处理。具体字段设计如下:

  1. 魔法值(Magic)
    用于标识协议的唯一性。每个消息包都会包含一个魔法值,接收方通过该魔法值判断消息是否符合当前协议。

  2. 帧类型(Frame Type)
    标识消息帧的类型,如普通消息、心跳(Ping/Pong)等。这个字段帮助接收方识别消息的内容和处理方式。

  3. 消息体长度(Length)
    表示消息体的大小,不包括协议头。这个字段对于解析和缓冲区管理非常重要,接收方可以根据该字段确定需要读取的字节数。

  4. 消息负载(Payload)
    实际的消息数据,通常是经过序列化的字节数组。这个字段包含了消息的核心内容。

  5. 序列号(Sequence Number)
    用于消息分片的排序和拼接。在多片消息的情况下,每个分片都有一个唯一的序列号,接收方依照序列号将分片拼装成完整的消息。

  6. 是否为最后一个分片(Is Last Fragment)
    用于标识当前分片是否为最后一个分片,接收方根据这个标志判断是否完成了所有分片的接收。

  7. 消息ID(Message ID)
    唯一标识一个消息。这个字段用于消息去重和区分不同的消息。

  8. 客户端ID(Client ID)
    用于标识发送消息的客户端。这个字段在多客户端环境中非常重要,可以确保消息不被错误地发送到其他客户端。

  9. 消息ID长度(Message ID Length)
    标识消息ID字段的长度,用于在反序列化时正确读取消息ID。

  10. 客户端ID长度(Client ID Length)
    标识客户端ID字段的长度,用于在反序列化时正确读取客户端ID。

二、工具类实现

为了实现对自定义协议的解析、分片和重组,我们需要一个工具类来进行相关操作。下面是 BinaryFrameParser 类的实现,包含了协议解析、分片解析和拼装多个分片为一个完整消息的方法。

1. 单帧解析:parse()

该方法负责解析一个完整的协议帧,首先检查是否有足够的数据来解析协议头(魔法值、帧类型、消息体长度),然后根据消息体的长度继续解析负载、序列号、分片标识、消息ID和客户端ID等字段。最终将这些字段封装成 CustomFrame 对象。

public static CustomFrame parse(ByteBuffer buffer) {
    // 检查是否有足够的数据来解析 magic、frameType、length
    if (buffer.remaining() < 2 + 1 + 4 + 2 + 2) {
        return null; // 不足够数据,无法解析
    }

    buffer.mark(); // 标记当前位置

    // 解析协议头部:magic + frameType + length
    short magic = buffer.getShort();
    byte frameType = buffer.get();
    int length = buffer.getInt();

    // 解析消息内容,计算总数据大小
    if (buffer.remaining() < length + 4 + 1 + 2 + 2) {
        buffer.reset();
        return null; // 不足够数据,无法解析完整消息
    }

    byte[] payload = new byte[length];
    buffer.get(payload);

    int sequenceNumber = buffer.getInt();
    boolean isLastFragment = buffer.get() == 1;

    // 解析 messageId 和 clientId 的长度字段
    short messageIdLength = buffer.getShort();
    byte[] messageIdBytes = new byte[messageIdLength];
    buffer.get(messageIdBytes);
    String messageId = new String(messageIdBytes, StandardCharsets.UTF_8);

    short clientIdLength = buffer.getShort();
    byte[] clientIdBytes = new byte[clientIdLength];
    buffer.get(clientIdBytes);
    String clientId = new String(clientIdBytes, StandardCharsets.UTF_8);

    return new CustomFrame(magic, frameType, length, payload, sequenceNumber, isLastFragment, messageId, clientId, messageIdLength, clientIdLength);
}
2. 分片解析:parseFragments()

该方法用于解析多个分片消息。通过调用 parse() 方法逐个解析每个分片,直到所有分片都被解析完成。

public static List<CustomFrame> parseFragments(ByteBuffer buffer) {
    List<CustomFrame> frames = new ArrayList<>();
    while (buffer.remaining() > 0) {
        CustomFrame frame = parse(buffer);
        if (frame != null) {
            frames.add(frame);
        }
    }
    return frames;
}
3. 分片重组:reassembleFragments()

该方法将所有分片根据序列号进行排序,确保正确拼接。然后,将所有分片的负载数据按顺序写入 ByteArrayOutputStream,最终返回完整的消息。

public static byte[] reassembleFragments(List<CustomFrame> frames) {
    // 按序列号排序分片,确保正确拼接
    frames.sort(Comparator.comparingInt(CustomFrame::getSequenceNumber));

    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    for (CustomFrame frame : frames) {
        outputStream.write(frame.getPayload(), 0, frame.getPayload().length);
    }
    return outputStream.toByteArray();
}
三、如何使用协议和工具类
  1. 客户端发送消息:
    客户端根据协议体的设计构建消息,使用 CustomFrame 对象进行序列化,并通过NIO发送到服务器端。

  2. 服务器端接收消息:
    服务器端接收到消息后,使用 BinaryFrameParser 进行解析。如果消息被分片,调用 parseFragments() 方法解析所有分片,最后使用 reassembleFragments() 方法将分片拼装成完整消息。

四、源码

   jingjiu/WebSockethttps://gitee.com/jingjiu11/web-socket

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值