Netty学习笔记_15(TCP粘包和拆包)

15 篇文章 0 订阅

1、TCP粘包与拆包

  • TCP面向连接,面向流,提供高可靠性服务。在消息收发过程中,需要在发送端和接收端建立成对的Socket,发送端不会一有数据就进行发送,而是将多次间隔较小的数据量较小的数据合并成一定长度的数据包整体发送。这样可以提高效率,但会给接收方分辨单个数据消息增加难度,因为面向流的通信是没有消息保护边界的
  • TCP粘包与拆包,是指发送端在发送多个数据消息时出现的不同情形。由于数据在发送前需要先转换为二进制字节码,当多个数据消息的字节码被合并成一个数据包发送时,称为粘包;当某个数据消息的字节码被划分到几个数据包内发送时,称为拆包粘包拆包可能使接收端解码数据包时出现错误。
  • TCP粘包和拆包的解决方案:使用自定义协议+编解码器解决,只要接收端能够知道每次读取数据的长度,就可以按位读取,避免出现读取错误。关键问题——使接收端知道每次读取数据的长度

2、TCP发送数据

  • 使用TCP协议,客户端向服务器发送10条数据,观察服务器端接收消息的情况;
  • 服务器myServer.java,服务器初始化类myServerInitializer.java,服务器自定义处理器myServerHandler.java。
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    
    public class myServer {
        public static void main(String[] args) throws InterruptedException {
    
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup();
    
            try{
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup,workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new mySeverInitializer());   //自己定义的初始化类
    
    
                ChannelFuture channelFuture = serverBootstrap.bind(7001).sync();
                channelFuture.channel().closeFuture().sync();
            }finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    
    
    /************************************************************************************************/
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.socket.SocketChannel;
    
    public class mySeverInitializer extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new myServerHandler());
        }
    }
    
    
    /************************************************************************************************/
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    
    import java.nio.charset.Charset;
    import java.util.UUID;
    
    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转成一个字符串
            String message = new String(buffer, Charset.forName("utf-8"));
    
            System.out.println("服务器接收到数据:" + message );
            System.out.println("服务器接收到消息量 = "+(++this.count));
    
            //服务器回送数据给客户端,随机ID
            ByteBuf byteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString()+"\n", Charset.forName("utf-8"));
            ctx.writeAndFlush(byteBuf);
    
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    
  • 客户端 myClient.java,客户端初始化类myClientInitializer.java,客户端自定义处理器myClientHandler.java

    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    
    public class myClient {
        public static void main(String[] args) throws InterruptedException {
            EventLoopGroup group = new NioEventLoopGroup();
    
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new myClientInitializer()); //自定义一个初始化对象
                ChannelFuture channelFuture = bootstrap.connect("localhost", 7001).sync();
    
                channelFuture.channel().closeFuture().sync();
            }finally {
                group.shutdownGracefully();
            }
        }
    }
    
    
    /************************************************************************************************/
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.socket.SocketChannel;
    
    
    public class myClientInitializer extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
    
            pipeline.addLast(new myClientHandler());
        }
    }
    
    
    /************************************************************************************************/
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    
    import java.nio.charset.Charset;
    
    public class myClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
        private int count;
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            //使用客户端循环发送10条数据
            for (int i=0;i<10;++i){
                ByteBuf buffer = Unpooled.copiedBuffer("hello,Server " + i, CharsetUtil.UTF_8);
                ctx.writeAndFlush(buffer);
            }
        }
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
            byte[] bytes = new byte[msg.readableBytes()];
            msg.readBytes(bytes);
    
            String message = new String(bytes, Charset.forName("utf-8"));
    
            System.out.println("客户端接收数据:"+message);
            System.out.println("客户端接收消息数量 = "+(++this.count));
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    

    通过多开应用,每个客户端向服务器发送这10条数据时,发送次数和每次发送数据量都不同,表明由于服务端不清楚客户端发送的数据长度,出现了粘包和拆包问题。

3、通过自定义协议对TCP数据包进行分割

3-1、案例要求

  1. 要求客户端发送5个message对象,客户端每次发送一个message对象
  2. 服务器端每次接收一个message,分5次进行解码,每读取一个message,会回送一个message对象给客户端

 3-2、案例分析

  1. 规定数据发送的形式:数据内容+数据长度。发送时先发送本次发送的数据长度,接收端接收后根据长度从数组中读取相应的数据内容。
  2. 自定义编码器和解码器,数据内容按照byte形式编解码,数据长度按照int形式编解码。

3-3、代码实现

  1. 客户端与服务器主程序与之前相同,不做赘述;
  2. 自定义协议MessageProtocol.java
    //协议包
    public class MessageProtocol {
        private int length;  //关键
        private byte[] context;
    
        public int getLength() {
            return length;
        }
    
        public byte[] getContext() {
            return context;
        }
    
        public void setLength(int length) {
            this.length = length;
        }
    
        public void setContext(byte[] context) {
            this.context = context;
        }
    }
    

     

  3. 自定义编码器myMessageEncoder.java
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.MessageToByteEncoder;
    
    public class myMessageEncoder extends MessageToByteEncoder<MessageProtocol> {
        @Override
        protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
            System.out.println("myMessageEncoder encode方法被调用");
            out.writeInt(msg.getLength());
            out.writeBytes(msg.getContext());
    
        }
    }
    

     

  4. 自定义解码器myMessageDecoder.java
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.ReplayingDecoder;
    
    import java.util.List;
    
    public class myMessageDecoder extends ReplayingDecoder<Void> {
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            System.out.println("myMessageDecoder decode方法被调用");
            //将得到的二进制字节码转换为 MessageProtocol 数据包
            int length = in.readInt();
            byte[] content = new byte[length];
    
            in.readBytes(content);
    
            //封装成MessageProtocol对象,放入out中交给下一个handler处理
            MessageProtocol messageProtocol = new MessageProtocol();
            messageProtocol.setLength(length);
            messageProtocol.setContext(content);
    
            out.add(messageProtocol);
        }
    }
    

     

  5. 服务器初始化类myServerInitializer.java需要在handler前添加编解码器:pipeline.addLast(...);
  6. 客户端初始化类myClientInitializer.java需要在handler前添加编解码器:pipeline.addLast(...);
  7. 自定义服务器业务处理器myServerHandler.java
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    
    import java.util.UUID;
    
    public class myServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {
        private int count;
    
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {
    
            //接收数据并处理
            int len = msg.getLength();
            byte[] context = msg.getContext();
    
            System.out.println("服务端接收到信息如下");
            System.out.println("数据长度:"+len);
            System.out.println("内容:"+new String(context, CharsetUtil.UTF_8));
    
            System.out.println("服务器接收到协议包数量 = "+(++this.count));
    
            //回复消息
            String response = UUID.randomUUID().toString();
            int responseLen = response.getBytes("utf-8").length;
            byte[] responseBytes = response.getBytes("utf-8");
            //构建一个协议包
            MessageProtocol messageProtocol = new MessageProtocol();
            messageProtocol.setLength(responseLen);
            messageProtocol.setContext(responseBytes);
    
            ctx.writeAndFlush(messageProtocol);
        }
    }
    

     

  8. 自定义客户端业务处理器myClientHandler.java
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    
    import java.nio.charset.Charset;
    
    public class myClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
        private int count;
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            //使用客户端循环发送10条数据
    
            for (int i=0;i<5;i++){
                String mes = "今天下雨,出门带伞";
                byte[] content = mes.getBytes(Charset.forName("utf-8"));
    
                int length = mes.getBytes(Charset.forName("utf-8")).length;
    
                //创建协议包
                MessageProtocol messageProtocol = new MessageProtocol();
                messageProtocol.setLength(length);
                messageProtocol.setContext(content);
    
                ctx.writeAndFlush(messageProtocol);
            }
        }
    
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println("异常消息 = "+cause.getMessage());
            ctx.close();
        }
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {
            int len = msg.getLength();
            byte[] msgContext = msg.getContext();
    
            System.out.println("客户端接收的消息如下:");
            System.out.println("消息长度 = "+len);
            System.out.println("消息内容 = "+new String(msgContext, CharsetUtil.UTF_8));
    
            System.out.println("客户端接收消息的数量 = "+(++this.count));
        }
    }
    

     

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值