Netty入门P12

拆包和粘包的例子

客户端示例代码:

/**
 * @program: learnnetty
 * @description: 客户端
 * @create: 2020-05-09 13:49
 **/
public class Client {
    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap
                .group(worker)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel
                                .pipeline()
                                .addLast(new ClientHandler());
                    }
                });

        bootstrap.connect("127.0.0.1", 8080);
    }
}

客户端处理器示例代码:

/**
 * @program: learnnetty
 * @description: 处理类
 * @create: 2020-05-09 13:53
 **/
public class ClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 1000; i++) {
            ByteBuf buf = getByteBuf(ctx);
            ctx.channel().writeAndFlush(buf);
            buf.release();
        }
    }

    private ByteBuf getByteBuf(ChannelHandlerContext ctx){
        byte[] bytes = "这是一条客户端发往服务端的消息".getBytes(StandardCharsets.UTF_8);
        return ctx.alloc().buffer().writeBytes(bytes);
    }
}

服务端示例代码:

/**
 * @program: learnnetty
 * @description: 服务端
 * @create: 2020-05-09 13:49
 **/
public class Server {
    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();
        NioEventLoopGroup boss = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                .group(boss, worker)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel
                                .pipeline()
                                .addLast(new ServerHandler());
                    }
                });
        serverBootstrap.bind(8080);
    }
}

服务端处理器示例代码:

/**
 * @program: learnnetty
 * @description: 处理类
 * @create: 2020-05-09 13:54
 **/
public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf)msg;
        System.out.println(new Date() + ",服务端收到:" + byteBuf.toString(StandardCharsets.UTF_8));
    }
}

出现问题的输出:

Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一�
Sat May 09 14:18:42 CST 2020,服务端收到:�客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的�

可以从控制台打印的数据看出:

  1. 输出正常的字符串;
  2. 输出多个字符串粘在一起的字符串;
  3. 输出一段不完整的字符串或者一段不完整的字符串粘在了其它字符串后边;

出现拆包粘包现象的原因

在应用层使用Netty框架来发送数据,Netty的底层则是以ByteBuf为单位发送数据包,但是操作系统的底层则是按照字节流来发送数据包,因此当数据到了服务端是以字节流方式读入,然后到Netty应用层重新拼装成ByteBuf,而此处的ByteBuf与客户端按照顺序发送的ByteBuf可能是不对等的。

拆包的思路

如果没有Netty,用户需要自己实现拆包,基本的思路的就是不断从TCP缓冲区读取数据,每次读完数据都需要判断一下是否是完整的数据包:

  1. 如果当前读取的数据不足以拼接成一个完整的数据包,那就保留此数据,继续从TCP缓存中读取数据,直到可以拼成一个完整的数据包;
  2. 如果当前的数据可以拼接成一个完整的数据包,那就将本次读取的数据和之前就存在的数据构成一个完整的数据包,多余的数据仍然保留,供下次读取数据尝试拼接;

Netty自带拆包器

Netty自带的拆包器主要有以下四种:

名称说明
FixedLengthFrameDecoder基于长度进行分包,适用于每包长度固定且协议简单的情况;
LineBasedFrameDecoder基于分割符进行拆包,即在发送数据包时,每包之间都是用换行符间隔;
DelimiterBasedFrameDecoder基于自定义分隔符进行拆包,即在发送数据时是用特定的符号进行分割;
LengthFieldBasedFrameDecoder基于长度域进行拆包,是用此拆包器需要在协议中规定长度域字段,拆包器会根据长度域内规定的长度进行拆包;

使用LengthFieldBasedFrameDecoder

声明一个数据包实体类:

/**
 * @program: learnnetty
 * @description: 数据包实体
 * @create: 2020-05-09 17:09
 **/
public class Frame {

    private byte flag;

    private int length;

    private String content;

    public byte getFlag() {
        return flag;
    }

    public void setFlag(byte flag) {
        this.flag = flag;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Frame{" +
                "flag=" + flag +
                ", length=" + length +
                ", content='" + content + '\'' +
                '}';
    }
}

此处的长度应该是转为byte数组之后的长度。

解码器:

/**
 * @program: learnnetty
 * @description: 解码
 * @create: 2020-05-09 17:25
 **/
public class FrameDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        //跳过标志位
        byteBuf.skipBytes(4);
        //读取数据域长度位
        int length = byteBuf.readInt();
        //读取数据
        byte[] data = new byte[length];
        byteBuf.readBytes(data);
        //生成实体
        Frame frame = new Frame();
        frame.setLength(length);
        frame.setContent(new String(data, StandardCharsets.UTF_8));
        list.add(frame);
    }
}

编码器:

/**
 * @program: learnnetty
 * @description: 编码
 * @create: 2020-05-09 17:25
 **/
public class FrameEncoder extends MessageToByteEncoder<Frame> {

    private static final int HEAD = 0x990718;

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Frame frame, ByteBuf byteBuf) throws Exception {
        byteBuf.writeInt(HEAD);
        byteBuf.writeInt(frame.getLength());
        byteBuf.writeBytes(frame.getContent().getBytes(StandardCharsets.UTF_8));
    }
}

客户端:

/**
 * @program: learnnetty
 * @description: 客户端
 * @create: 2020-05-09 13:49
 **/
public class Client {
    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap
                .group(worker)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel
                                .pipeline()
                                .addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 4, 4))
                                .addLast(new FrameDecoder())
                                .addLast(new ClientHandler())
                                .addLast(new FrameEncoder());
                    }
                });

        bootstrap.connect("127.0.0.1", 8080);
    }
}

服务端:

/**
 * @program: learnnetty
 * @description: 服务端
 * @create: 2020-05-09 13:49
 **/
public class Server {
    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();
        NioEventLoopGroup boss = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                .group(boss, worker)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel
                                .pipeline()
                                .addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 4, 4))
                                .addLast(new FrameDecoder())
                                .addLast(new ServerHandler())
                                .addLast(new FrameEncoder());
                    }
                });
        serverBootstrap.bind(8080);
    }
}

客户端处理器:

/**
 * @program: learnnetty
 * @description: 处理类
 * @create: 2020-05-09 13:53
 **/
public class ClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 1000; i++) {
            Frame frame = new Frame();
            frame.setContent("这是一条客户端发往服务端的消息");
            frame.setLength(frame.getContent().getBytes(StandardCharsets.UTF_8).length);
            ctx.channel().writeAndFlush(frame);
        }
    }
}

服务端处理器:

/**
 * @program: learnnetty
 * @description: 处理类
 * @create: 2020-05-09 13:54
 **/
public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Frame frame = (Frame)msg;
        System.out.println(new Date() + ",服务端收到:" + frame.getContent());
    }
}

服务端输出(部分):

Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息

可以看到,输出中并没有出现拆包和粘包的现象。

拒绝非目标协议数据包

添加发送其他头部报文的客户端:

/**
 * @program: learnnetty
 * @description: 编码
 * @create: 2020-05-09 17:25
 **/
public class FrameEncoder extends MessageToByteEncoder<Frame> {

    private static final int HEAD = 0x990718;

    private static final int FAKE_HEAD = 0x123456;

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Frame frame, ByteBuf byteBuf) throws Exception {
        Random random = new Random();
        if (random.nextInt(11) > 6){
            byteBuf.writeInt(FAKE_HEAD);
            String fakeMsg = "这是虚假的信息";
            byteBuf.writeInt(fakeMsg.getBytes(StandardCharsets.UTF_8).length);
            byteBuf.writeBytes(fakeMsg.getBytes(StandardCharsets.UTF_8));
        }else {
            byteBuf.writeInt(HEAD);
            byteBuf.writeInt(frame.getLength());
            byteBuf.writeBytes(frame.getContent().getBytes(StandardCharsets.UTF_8));
        }
    }
}

服务端输出:

Sat May 09 18:29:59 CST 2020,服务端收到:这是虚假的信息
Sat May 09 18:29:59 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:29:59 CST 2020,服务端收到:这是虚假的信息
Sat May 09 18:29:59 CST 2020,服务端收到:这是虚假的信息
Sat May 09 18:29:59 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:29:59 CST 2020,服务端收到:这是一条客户端发往服务端的消息

可以看到有许多非法报文被读取了。

创建自定义的LengthFieldBasedFrameDecoder:

/**
 * @program: learnnetty
 * @description: 拆包器
 * @create: 2020-05-09 18:31
 **/
public class Spliter extends LengthFieldBasedFrameDecoder {

    public Spliter(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        //检查头部标志位
        if (in.getInt(in.readerIndex()) != FrameEncoder.HEAD){
            //如果收到不正确的数据包就断连
            ctx.channel().close();
            return null;
        }
        return super.decode(ctx, in);
    }
}

使用自定义的LengthFieldBasedFrameDecoder:

/**
 * @program: learnnetty
 * @description: 服务端
 * @create: 2020-05-09 13:49
 **/
public class Server {
    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();
        NioEventLoopGroup boss = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                .group(boss, worker)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel
                                .pipeline()
                                .addLast(new Spliter(Integer.MAX_VALUE, 4, 4))
                                .addLast(new FrameDecoder())
                                .addLast(new ServerHandler())
                                .addLast(new FrameEncoder());
                    }
                });
        serverBootstrap.bind(8080);
    }
}

服务端输出:

Sat May 09 18:35:12 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:35:12 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:35:12 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:35:12 CST 2020,服务端收到:这是一条客户端发往服务端的消息

当前能做到的仅仅是收到错误报文后断连。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值