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 的 ChannelHandlerContext
的 channelRead
方法,累积接收到的小数据包,并判断是否已经接收到一个完整的数据包。如果是,则进行相应的处理。
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
评论区