Netty解决TCP粘包拆包问题

TCP粘包与拆包

TCP是面向连接、面向流的、可靠的服务

客户端与服务器端具有成对的socket,通过优化算法Nagle将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。提高了效率,但是接收端难于分辨完整数据包。因为面向流的通讯是无消息保护边界的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQXL0sny-1606447386773)(C:\Users\acer\AppData\Roaming\Typora\typora-user-images\image-20201127091019686.png)]

解决方案

1、使用自定义协议(封装成类)+编解码器

2、解决服务器端每次读取数据长度的问题

例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtiL08CL-1606447386788)(C:\Users\acer\AppData\Roaming\Typora\typora-user-images\image-20201127100445316.png)]

自定义协议(封装的类)

public class MessageProtocol {
    private int len; //消息长度
    private byte[] content; //消息内容

    public int getLen() {
        return len;
    }
    public void setLen(int len) {
        this.len = len;
    }
    public byte[] getContent() {
        return content;
    }
    public void setContent(byte[] content) {
        this.content = content;
    }
}

客户端

public class MyClient {
    public static void main(String[] args) throws Exception{
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new MyClientInitialize());
            ChannelFuture channelFuture = bootstrap.connect("localhost", 9999).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            workerGroup.shutdownGracefully();
        }
    }
}
public class MyClientInitialize extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //加入编码器 用于发送消息给服务器端
        pipeline.addLast(new MyMessageEncoder());
        //加入解码器 用于接收服务器的消息
        pipeline.addLast(new MyMessageDecoder());
        //加入自定义Handler
        pipeline.addLast(new MyClientHandler());
    }
}
public class MyClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
    int count; //打印第几次接收消息
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {
        int len = msg.getLen();
        byte[] content = msg.getContent();
        System.out.println("客户端接收到的消息如下");
        System.out.println("长度=" + len);
        System.out.println("内容=" + new String(content, Charset.forName("utf-8")));
        System.out.println("客户端接收到数据包的数量="+(++this.count));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 5; ++i) {
            String msg = "Hello,Server";
            byte[] bytes = msg.getBytes(Charset.forName("utf-8"));
            int length = msg.getBytes(Charset.forName("utf-8")).length;

            //创建一个协议包
            MessageProtocol messageProtocol = new MessageProtocol();
            messageProtocol.setLen(length);
            messageProtocol.setContent(bytes);
            ctx.writeAndFlush(messageProtocol);
        }
    }

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

编码器、解码器

//编码器
public class MyMessageEncoder extends MessageToByteEncoder<MessageProtocol> {
    @Override
    protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
        //将Handler传来的消息进行发送
        out.writeInt(msg.getLen()); 
        out.writeBytes(msg.getContent());
    }
}
//解码器
public class MyMessageDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("MyMessageDecoder  decoder 被调用了");
        int len = in.readInt();
        byte[] content = new byte[len];
        in.readBytes(content);
        //封装成MessageProtocol对象传给Handler业务处理
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLen(len);
        messageProtocol.setContent(content);
        out.add(messageProtocol);
    }
}

服务器

public class MyServer {
    public static void main(String[] args) throws Exception {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new MyServerInitialize());

            ChannelFuture channelFuture = serverBootstrap.bind(9999).sync();
            channelFuture.channel().closeFuture().sync();

        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
public class MyServerInitialize extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();      
        //加入编码器 用于发送消息给服务器端
        pipeline.addLast(new MyMessageEncoder());
        //加入解码器 用于接收服务器的消息
        pipeline.addLast(new MyMessageDecoder());
        //加入自定义Handler
        pipeline.addLast(new MyServerHandler());
    }
}
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {
    int count;
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {
        //接收到数据进行处理
        int len = msg.getLen();
        byte[] content = msg.getContent();
        System.out.println("服务器接收到的消息:");
        System.out.println("长度=" + len);
        System.out.println("内容=" + new String(content, Charset.forName("utf-8")));
        System.out.println("服务器接收到消息包数量="+(++this.count));

        //每次接收到客户端传来的消息进行回复
        String responseContent = UUID.randomUUID().toString();
        byte[] content1 = responseContent.getBytes(Charset.forName("utf-8"));
        int len1 = responseContent.getBytes(Charset.forName("utf-8")).length;
        //构建一个协议包
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLen(len1);
        messageProtocol.setContent(content1);
        ctx.writeAndFlush(messageProtocol);
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值