当客户端发送过大数据时,netty分包解决方案
遇到问题:当在一个项目中通过socket向netty服务器一次性发送近40kb的数据时,在netty服务端发生分包。
服务端代码:
public class HeartBeatServer {
private static int port = 5200;
@SuppressWarnings("static-access")
public HeartBeatServer(int port) {
this.port = port;
}
ServerBootstrap bootstrap = null;
ChannelFuture f;
public static void main(String args[]) {
HeartBeatServer heartBeatServer = new HeartBeatServer(port);
heartBeatServer.startServer();
}
public void startServer() {
EventLoopGroup bossgroup = new NioEventLoopGroup();
EventLoopGroup workergroup = new NioEventLoopGroup();
try {
bootstrap = new ServerBootstrap();
bootstrap.group(bossgroup, workergroup)
.channel(NioServerSocketChannel.class)
.childHandler(new HeartBeatServerInitializer());
f = bootstrap.bind(port).sync();
System.out.println("server start ,port: "+port);
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossgroup.shutdownGracefully();
workergroup.shutdownGracefully();
}
}
private class HeartBeatServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("handler", new HeartbeatServerHandler());
}
}
}
解决方案如下:
ByteBuf delimiter = Unpooled.copiedBuffer("\t".getBytes());
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(1024,delimiter));
DelimiterBasedFrameDecoder的构造方法
public DelimiterBasedFrameDecoder(int maxFrameLength, boolean stripDelimiter, ByteBuf delimiter) {
this(maxFrameLength, stripDelimiter, true, delimiter);
}
maxFrameLength:解码的帧的最大长度
stripDelimiter:解码时是否去掉分隔符
failFast:为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常
delimiter:分隔符
同样,客户端需要在发送消息的尾部加入一个"\t",不用担心服务端,服务端在接受时会自动忽略到这个\t。
分包解决方案
1.消息长度固定,累计读取到消息长度总和为定长Len的报文之后即认为是读取到了一个完整的消息。计数器归位,重新读取。(FixedLengthFrameDecoder)
2.将回车换行符作为消息结束符。(LineBasedframeDecoder)
3.将特殊的分隔符作为消息分隔符,回车换行符是他的子集。(DelimiterBasedFrameDecoder)
4.通过在消息头定义长度字段来标识消息总长度。
四种解决方案中我选用了第三种
LineBasedframeDecoder属于第二种,今天我们要说的DelimiterBasedFrameDecoder和FixedLengthFrameDecoder属于第三种和第一种。DelimiterBasedFrameDecoder用来解决以特殊符号作为消息结束符的粘包问题,FixedLengthFrameDecoder用来解决定长消息的粘包问题。
第三种我们已经用了,那么来说一下第一种和第二种
FixedLengthFrameDecoder
FixedLengthFrameDecoder是固定长度解码器
pipeline.addLast(new FixedLengthFrameDecoder(23));//参数为一次接受的数据长度
这个的意思是只接收到23个字节,如果字节多余23个也不接受了直接给到channelread方法中,如果没有接够23个字节,则会一直阻塞。
FixedLengthFrameDecoder构造方法如下
public FixedLengthFrameDecoder(int frameLength, boolean allocateFullBuffer) {
if (frameLength <= 0) {
throw new IllegalArgumentException(
"frameLength must be a positive integer: " + frameLength);
}
this.frameLength = frameLength;
this.allocateFullBuffer = allocateFullBuffer;
}
allocateFullBuffer默认为false
LineBasedFrameDecoder
按行分割协议
LineBasedFrameDecoder 和LineEncoder采用的通信协议非常简单,即按照行进行分割,遇到一个换行符,则认为是一个完整的报文。在发送方,使用LineEncoder为数据添加换行符;在接受方,使用LineBasedFrameDecoder对换行符进行解码。
LineBasedFrameDecoder采用的通信协议格式非常简单:使用换行符\n或者\r\n作为依据,遇到\n或者\r\n都认为是一条完整的消息。
LineBasedFrameDecoder提供了2个构造方法,如下:
public LineBasedFrameDecoder(final int maxLength) {
this(maxLength, true, false);
}
public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
this.maxLength = maxLength;
this.failFast = failFast;
this.stripDelimiter = stripDelimiter;
}
maxLength:
表示一行最大的长度,如果超过这个长度依然没有检测到\n或者\r\n,将会抛出TooLongFrameException
failFast:
与maxLength联合使用,表示超过maxLength后,抛出TooLongFrameException的时机。如果为true,则超出maxLength后立即抛出TooLongFrameException,不继续进行解码;如果为false,则等到完整的消息被解码后,再抛出TooLongFrameException异常。
stripDelimiter:
解码后的消息是否去除\n,\r\n分隔符。
使用例
// 使用LineBasedFrameDecoder解决粘包问题,其会根据"\n"或"\r\n"对二进制数据进行拆分
pipeline().addLast(new LineBasedFrameDecoder(1024, true, true));