当客户端发送过大数据时,netty分包解决方案

2020-03-28  本文已影响0人  凉风拂面秋挽月

遇到问题:当在一个项目中通过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));
上一篇下一篇

猜你喜欢

热点阅读