从NIO到netty(16) Netty的粘包拆包问题与解决方案

56 篇文章 2 订阅

关于粘包和拆包问题的产生,我在dubbo源码解析中已经解释过了,这里用一个例子来演示如果不加相应的处理器,产生的问题

服务端handler

public class MyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
    private int  count ;
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        byte[] buffer = new byte[msg.readableBytes()] ;
        msg.readBytes(buffer);//注意buffer的长度必须和msg.readableBytes()一样,否则报错,这是netty规定的
        String message = new String(buffer, Charset.forName("utf-8"));
        System.out.println("服务端接收到的消息:"+message);
        System.out.println("服务端接收到的消息数量:"+(++this.count));

        ByteBuf responseMessage = Unpooled.copiedBuffer(UUID.randomUUID().toString(),Charset.forName("utf-8"));
        ctx.writeAndFlush(responseMessage);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
客户端handler

public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    private int count;
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        byte[] buffer = new byte[msg.readableBytes()];
        msg.readBytes(buffer);

        String message = new String(buffer,Charset.forName("utf-8"));
        System.out.println("客户端接收到的消息内容:"+message);
        System.out.println("客户端接收到的消息数量:"+(++this.count));

    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for(int i = 0;i <10;i++){
            ByteBuf buffer  = Unpooled.copiedBuffer("sent from client",Charset.forName("utf-8"));
            ctx.writeAndFlush(buffer);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        ctx.close();
    }
}

在pipline中只添加handler

public class MyClientIniatializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipline = ch.pipeline();

        pipline.addLast(new MyClientHandler());
    }
}

服务端打印结果:

服务端接收到的消息:sent from clientsent from clientsent from clientsent from clientsent from clientsent from clientsent from clientsent from clientsent from clientsent from client
服务端接收到的消息数量:1
客户端打印结果:

客户端接收到的消息内容:8c260c09-8a9d-4491-9bf7-ff5c5c8f790c
客户端接收到的消息数量:1

很多人认为客户端应该收到10条UUID才对,但是这里协议进行了粘包,将客户端的10条消息作为一条消息发给我服务端,才导致服务端只打印了一天消息(10条客户端消息的集合)而且只接受了一次,因此服务端打印的接收数量是1。

此时我们把客户端关闭,然后重新连接服务端,我们重复2次这个过程。 

服务端接收到的消息:sent from clientsent from clientsent from clientsent from clientsent from clientsent from clientsent from clientsent from clientsent from clientsent from client
服务端接收到的消息数量:1
服务端接收到的消息:sent from client
服务端接收到的消息数量:1
服务端接收到的消息:sent from client
服务端接收到的消息数量:2
服务端接收到的消息:sent from clientsent from clientsent from client
服务端接收到的消息数量:3
服务端接收到的消息:sent from clientsent from client
服务端接收到的消息数量:4
服务端接收到的消息:sent from clientsent from clientsent from client
服务端接收到的消息数量:5
服务端接收到的消息:sent from client
服务端接收到的消息数量:1
服务端接收到的消息:sent from client
服务端接收到的消息数量:2
服务端接收到的消息:sent from clientsent from clientsent from clientsent from client
服务端接收到的消息数量:3
服务端接收到的消息:sent from clientsent from client
服务端接收到的消息数量:4
服务端接收到的消息:sent from clientsent from client
服务端接收到的消息数量:5
--------------------------------------------------

下面提供解决方案:

首先自定义协议:

public class PersonProtocal {
    private int length;

    private byte[] content;

    public int getLength() {
        return length;
    }

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

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }

}
服务端handler

public class MyServerHandler extends SimpleChannelInboundHandler<PersonProtocal> {
    private int count;
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, PersonProtocal msg) throws Exception {
        System.out.println("服务端接受到的数据:");
        System.out.println("数据长度:"+msg.getLength());
        System.out.println("数据内容:"+ new String(msg.getContent(), Charset.forName("utf-8")) );
        System.out.println("服务端接收到的消息数量:"+(++count));

        String responseMessage = UUID.randomUUID().toString();
        int responseLength = responseMessage.getBytes(Charset.forName("utf-8")).length;
        byte[] responseContent = responseMessage.getBytes(Charset.forName("utf-8"));
        PersonProtocal personProtocal = new PersonProtocal();
        personProtocal.setLength(responseLength);
        personProtocal.setContent(responseContent);
        ctx.writeAndFlush(personProtocal);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
客户端handler

public class MyClientHandler extends SimpleChannelInboundHandler<PersonProtocal> {

    private int count;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for(int i=0;i<10;i++){
            String messaage = "sent from client";
            int length = messaage.getBytes(Charset.forName("utf-8")).length;
            byte[] content = messaage.getBytes(Charset.forName("utf-8"));
            PersonProtocal personProtocal = new PersonProtocal();
            personProtocal.setLength(length);
            personProtocal.setContent(content);
            ctx.writeAndFlush(personProtocal);
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, PersonProtocal msg) throws Exception {
        int length = msg.getLength();
        byte[] content = msg.getContent();
        System.out.println("客户端接收到的消息");
        System.out.println("长度:"+length);
        System.out.println("消息内容:"+new String(content,Charset.forName("utf-8")));
        System.out.println("客户端接收到的消息数量:"+(++this.count));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
然后我们自定义一个解码器

public class MyPersonDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("MyPersonDecoder decode invoked ");
        int length = in.readInt();
        byte[] content = new byte[length];
        in.readBytes(content);

        PersonProtocal personProtocal = new PersonProtocal();
        personProtocal.setLength(length);
        personProtocal.setContent(content);

        out.add(personProtocal);
    }
}
自定义解码器

public class MyPersonEncoder extends MessageToByteEncoder<PersonProtocal> {

    @Override
    protected void encode(ChannelHandlerContext ctx, PersonProtocal msg, ByteBuf out) throws Exception {
        System.out.println("MyPersonEncoder encode invoked");
        out.writeInt(msg.getLength());
        out.writeBytes(msg.getContent());
    }
}

客户端和服务端的pipeline中都加入自定义的编解码器
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
       ChannelPipeline channelPipeline =  ch.pipeline();
        channelPipeline.addLast(new MyPersonDecoder());
        channelPipeline.addLast(new MyPersonEncoder());
        channelPipeline.addLast(new MyServerHandler());
    }
}
客户端启动之后,向服务端发送了十条“sent from client”客户端经过编码器MyPersonEncoder发送出去, 然后服务端接收到之后先经过解码器MyPersonDecoder,解码成PersonProtocal实体,然后打印消息内容,服务端每接收到一条“sent from client”,紧接着向客户端发送一个uuid(讲过编码器MyPersonEncoder),之后客户端收到uuid,经过解码器MyPersonDecoder转换成PersonProtocal实体。 
服务端打印结果:

MyPersonDecoder decode invoked 
服务端接受到的数据:
数据长度:16
数据内容:sent from client
服务端接收到的消息数量:1
MyPersonEncoder encode invoked
MyPersonDecoder decode invoked 
服务端接受到的数据:
数据长度:16
数据内容:sent from client
服务端接收到的消息数量:2
MyPersonEncoder encode invoked
MyPersonDecoder decode invoked 
服务端接受到的数据:
数据长度:16
数据内容:sent from client
服务端接收到的消息数量:3
MyPersonEncoder encode invoked
MyPersonDecoder decode invoked 
服务端接受到的数据:
数据长度:16
数据内容:sent from client
服务端接收到的消息数量:4
MyPersonEncoder encode invoked
MyPersonDecoder decode invoked 
服务端接受到的数据:
数据长度:16
数据内容:sent from client
服务端接收到的消息数量:5
MyPersonEncoder encode invoked
MyPersonDecoder decode invoked 
服务端接受到的数据:
数据长度:16
数据内容:sent from client
服务端接收到的消息数量:6
MyPersonEncoder encode invoked
MyPersonDecoder decode invoked 
服务端接受到的数据:
数据长度:16
数据内容:sent from client
服务端接收到的消息数量:7
MyPersonEncoder encode invoked
MyPersonDecoder decode invoked 
服务端接受到的数据:
数据长度:16
数据内容:sent from client
服务端接收到的消息数量:8
MyPersonEncoder encode invoked
MyPersonDecoder decode invoked 
服务端接受到的数据:
数据长度:16
数据内容:sent from client
服务端接收到的消息数量:9
MyPersonEncoder encode invoked
MyPersonDecoder decode invoked 
服务端接受到的数据:
数据长度:16
数据内容:sent from client
服务端接收到的消息数量:10
MyPersonEncoder encode invoked

客户端的打印结果:

MyPersonEncoder encode invoked
MyPersonEncoder encode invoked
MyPersonEncoder encode invoked
MyPersonEncoder encode invoked
MyPersonEncoder encode invoked
MyPersonEncoder encode invoked
MyPersonEncoder encode invoked
MyPersonEncoder encode invoked
MyPersonEncoder encode invoked
MyPersonEncoder encode invoked
MyPersonDecoder decode invoked 
客户端接收到的消息
长度:36
消息内容:aeb03767-5b48-4c1b-ae08-2c643fa511f1
客户端接收到的消息数量:1
MyPersonDecoder decode invoked 
客户端接收到的消息
长度:36
消息内容:22a19d24-3a53-4954-af51-6d8a47a31412
客户端接收到的消息数量:2
MyPersonDecoder decode invoked 
客户端接收到的消息
长度:36
消息内容:0bb4cbff-8725-4aa1-b431-81d185639dd0
客户端接收到的消息数量:3
MyPersonDecoder decode invoked 
客户端接收到的消息
长度:36
消息内容:4770c9ec-8868-4253-a6be-632105bc677c
客户端接收到的消息数量:4
MyPersonDecoder decode invoked 
客户端接收到的消息
长度:36
消息内容:07562dc9-bde7-42bf-91b5-a964d320a4ab
客户端接收到的消息数量:5
MyPersonDecoder decode invoked 
客户端接收到的消息
长度:36
消息内容:08955003-5487-47a4-92c0-3fd6d636abbd
客户端接收到的消息数量:6
MyPersonDecoder decode invoked 
客户端接收到的消息
长度:36
消息内容:4e35d4d9-5780-46df-a78a-8bc0bf293e03
客户端接收到的消息数量:7
MyPersonDecoder decode invoked 
客户端接收到的消息
长度:36
消息内容:051d7e80-8e1c-4b0a-979e-9ab390c9d139
客户端接收到的消息数量:8
MyPersonDecoder decode invoked 
客户端接收到的消息
长度:36
消息内容:a220680e-852f-4525-9ec4-06f7912dcabe
客户端接收到的消息数量:9
MyPersonDecoder decode invoked 
客户端接收到的消息
长度:36
消息内容:64bf6571-e246-4f4e-89f5-afe609ccc4ed
客户端接收到的消息数量:10
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值