java netty使用DelimiterBasedFrameDecoder处理tcp粘包问题

1、问题概述
tcp产生粘包问题的原因有

  • 应用程序write写入的字节大小大于套接字发送缓冲区的大小。
  • 进行MSS(TCP的数据部分)大小的TCP分段。
  • 以太网帧的payload大于MTU进行IP分片

业界解决方法
tcp粘包的问题只能通过上层的应用协议栈来设计解决,根据业界的主流协议的解决方案,可以归纳如下。

  • 消息定长,例如每个报文的大小固定,例如固定为100字节,如果长度不够,可以用空白填充。
  • 在包尾增加回车换行符进行分割,例如FTP协议。
  • 将消息分为消息头和消息体,消息头中包含表示消息的总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度

netty粘包处理
netty提供了多种编码器用于处理半包,这些编码器包含

  • LineBasedFrameDecoder 时间解码器
  • DelimiterBasedFrameDecoder 分隔符解码器
  • FixedLengthFrameDecoder 定长解码器

这里介绍使用DelimiterBasedFrameDecoder 来处理包过长,发送的测试数据如下:

don't know what I do now is right, those are wrong, and when I finally Laosi when I know these. So I can do now is to try to do well in everything, and then wait to die a natural death.Sometimes I can be very happy to talk to everyone, can be very presumptuous, but no one knows, it is but very deliberatelycamouflage, camouflage; I can make him very happy very happy, but couldn't find the source of happiness, just giggle
If not to the sun for smiling, warm is still in the sun there, but wewill laugh more confident calm; if turned to found his own shadow, appropriate escape, the sun will be through the heart,warm each place behind the corner; if an outstretched palm cannot fall butterfly, then clenched waving arms, given power; if I can't have bright smile, it will face to the sunshine, and sunshine smile together, in full bloom.
Time is like a river, the left bank is unable to forget the memories, right is worth grasp the youth, the middle of the fast flowing, is the sad young faint. There are many good things, buttruly belong to own but not much. See the courthouse blossom,honor or disgrace not Jing, hope heaven Yunjuanyunshu, has no intention to stay. In this round the world, all can learn to use a normal heart to treat all around, is also a kind of realm!

这里的数据超过1024字节的长度,正常情况下这个tcp会产生粘包或者拆包现在,结果如图所示:
这里写图片描述
可以看出这段数据被拆成了两断,下面使用DelimiterBasedFrameDecoder 处理这个问题,处理数据的源码如下所示:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.io.UnsupportedEncodingException;

public class TimeServerHandler extends ChannelInboundHandlerAdapter {

    private int counter;
    //有连接可以读取
    @Override
    public void channelRead(ChannelHandlerContext ctx,Object msg){

        String body = (String) msg;
        try {
            System.out.println("The time server receive order :" +(++counter)+":"+ body);
            String currentTime = System.currentTimeMillis()+"";
            ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
            ctx.write(resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx){
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
        System.out.println(cause.toString());
        ctx.close();
    }
}

启动初始化类如下:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

public class TimeServer {

    public void bind(int port) throws Exception {

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try{
        ServerBootstrap b = new ServerBootstrap();
        //最大连接数量设定 BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
        b.group(bossGroup,workGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,1024).childHandler(new ChildChannelHandler());
        //绑定端口,等待同步成功
        ChannelFuture f = b.bind(port).sync();
        f.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        protected void initChannel(SocketChannel socketChannel) throws Exception {

            ByteBuf delimiter = Unpooled.copiedBuffer("$_$".getBytes());
            socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(2048,delimiter));
            socketChannel.pipeline().addLast(new StringDecoder());
            socketChannel.pipeline().addLast(new TimeServerHandler());
        }
    }

    public static void main(String[] args){
        int port = 9000;
        try {
            new TimeServer().bind(port);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在测试数据最后加上$_$,再次使用网络助手进行测试,如下图所示,tcp粘包问题得以解决
这里写图片描述

展开阅读全文

没有更多推荐了,返回首页