侧边栏壁纸
博主头像
ZHD的小窝博主等级

行动起来,活在当下

  • 累计撰写 79 篇文章
  • 累计创建 53 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

Spring Cloud gateway转发websocket报Max frame length of 65536 has been exceeded

江南的风
2024-11-14 / 0 评论 / 0 点赞 / 18 阅读 / 5372 字 / 正在检测是否收录...

1. 原因

spring cloud代理websocket底层使用的是Netty,Netty默认的包大小是64Kb

如果传比较大的图片或其他数据时会报如下错误:

2. 解决办法

提供了两种解决办法,第一种是在源头上解决,第二种是取巧的方式解决。

2.1. 方法一:客户端实现拆包发送

  • 拆分数据包(发送端)

// content 为原有数据包
while(content.isReadable()) {
    int proPacketSize = Math.min(content.readableBytes(), 65536);// 每个数据包的最大长度 64Kb
    // 拆分数据包
    ByteBuf proP = content.readSlice(proPacketSize).retain();
    // 发送
    ctx.writeAndFlush(new BinaryWebSocketFrame(proP));

}

content = null;
  • 解决半包问题

告知接收端接收到什么标识后认为整包发送完成,可以进行合并,这里有两种方式:发送长度、发送特殊标识

我们以发送整包的长度为例,在消息体最开始的位置,添加长度

// 这里声明一个新的 ByteBuf
ByteBuf byteBuf = ctx.alloc().buffer();
// header 4个byte
byteBuf.writeInt(content.readableBytes());
// body
byteBuf.writeBytes(content);

// 原来的消息体用不到了,手动释放掉
ReferenceCountUtil.release(content);

while (byteBuf.isReadable()) {
    int proPacketSize = Math.min(byteBuf.readableBytes(), 65536);
    ByteBuf proP = byteBuf.readSlice(proPacketSize).retain();
    ctx.writeAndFlush(new BinaryWebSocketFrame(proP));
}

// 消息体用不到了,手动释放掉
ReferenceCountUtil.release(byteBuf);
  • 组装数据包(接收端)

在接收端,通过 Netty 的 ChannelHandlerContextchannelRead 方法,累积接收到的小数据包,并判断是否已经接收到一个完整的数据包。如果是,则进行相应的处理。

private int expectedLength = -1;

private ByteBuf cumulativeBuffer = Unpooled.buffer();

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
     // 这里因为是 WebSocket 协议,接收到的数据包为 WebSocketFrame
     if (msg instanceof WebSocketFrame) {
         WebSocketFrame frame = (WebSocketFrame) msg;
         ByteBuf content = frame.content();

        if (expectedLength == -1 && content.readableBytes() > 4) {
            expectedLength = content.readInt();
            if (expectedLength <= -1) {
                releaseCumulativeBuffer();
                super.channelRead(ctx, msg);
                return;
            }
        }
         
         // 累积数据包
         cumulativeBuffer.writeBytes(content);
         
         // 根据判断,判断是否已经接收到完整数据包
         if (isCompletePacket(cumulativeBuffer)) {
             try {
                 // 处理完整数据包 cumulativeBuffer
             } finally {
                 // 最终释放 cumulativeBuffer 数据包的内存
                ReferenceCountUtil.release(cumulativeBuffer);
                cumulativeBuffer = Unpooled.buffer();
             }
         }
     } else {
         super.channelRead(ctx, msg);
     }
}
private void releaseCumulativeBuffer() {
    try {
        expectedLength = -1;
        ReferenceCountUtil.release(cumulativeBuffer);
        cumulativeBuffer = Unpooled.buffer();
    } catch (Throwable e) {
        // ignore
    }
}
private boolean isCompletePacket(ByteBuf cumulativeBuffer) {
   return cumulativeBuffer.readableBytes() == expectedLength;
}

2.2. 方法二:覆盖掉DEFAULT_FRAME_MAX_SIZE

注意:我使用的gateway版本是3.1.4,不同版本修改会有些差异。

我们发现maxFramePayloadLength是在WebsocketSpec这个接口里面定义的

  • 工程内创建这个接口的包名:reactor.netty.http.websocket

  • 将WebsocketSpec类复制一份到上面创建的包名下,并且修改WebsocketSpec中的maxFramePayloadLength

0

评论区