Netty编解码器与TCP粘包

编解码器

介绍

  • 当Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会被解码:从字节转换为另一种格式(比如java对象);如果是出站消息,它会被编码成字节。
  • Netty提供一系列实用的编解码器,他们都实现了ChannelInboundHadnler或者ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了
    • 以入站为例,对于每个从入站Channel读取的消息,这个方法会被调用。随后,它将调用由解码器所提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。

自定编解码器案例

编/解码器

  • LongToByteEncoder
    public class LongToByteEncoder extends MessageToByteEncoder {
        //编码方法
        @Override
        protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
    
            System.out.println("MyLongToByteEncoder encode 被调用");
            System.out.println("msg=" + msg);
            out.writeLong(msg);
    
        }
    }
    
  • ByteToLongDecoder
    public class ByteToLongDecoder extends ByteToMessageDecoder {
        /**
         *
         * decode 会根据接收的数据,被调用多次, 直到确定没有新的元素被添加到list
         * , 或者是ByteBuf 没有更多的可读字节为止
         * 如果list out 不为空,就会将list的内容传递给下一个 channelinboundhandler处理, 该处理器的方法也会被调用多次
         *
         * @param ctx 上下文对象
         * @param in 入站的 ByteBuf
         * @param out List 集合,将解码后的数据传给下一个handler
         * @throws Exception
         */
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
    
            System.out.println("MyByteToLongDecoder 被调用");
            //因为 long 8个字节, 需要判断有8个字节,才能读取一个long
            if(in.readableBytes() >= 8) {
                out.add(in.readLong());
            }
        }
    }
      
       
      

    服务端

    • ServerHandler
      public class ServerHandler extends SimpleChannelInboundHandler {
          @Override
          protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
      
              System.out.println("从客户端" + ctx.channel().remoteAddress() + " 读取到long " + msg);
      
              //给客户端发送一个long
              ctx.writeAndFlush(98765L);
          }
      
          @Override
          public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
              cause.printStackTrace();
              ctx.close();
          }
      }
      
    • YzxServer
      public class YzxServer {
          public static void main(String[] args) throws Exception{
      
              EventLoopGroup bossGroup = new NioEventLoopGroup(1);
              EventLoopGroup workerGroup = new NioEventLoopGroup();
      
              try {
      
                  ServerBootstrap serverBootstrap = new ServerBootstrap();
                  serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer(){
      	            @Override
      			    protected void initChannel(SocketChannel ch) throws Exception {
      			        ChannelPipeline pipeline = ch.pipeline();
      			
      			        //入站的handler进行解码 MyByteToLongDecoder
      			        //pipeline.addLast(new ByteToLongDecoder());
      			        //在MyByteToLongDecoder解码后,使用自定义的handler 处理业务逻辑
      			        pipeline.addLast(new ServerHandler());
      
      					//出站的handler进行编码
      			        pipeline.addFirst(new LongToByteEncoder());
      			    }
                  }); 
      
      
                  ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
                  channelFuture.channel().closeFuture().sync();
      
              }finally {
                  bossGroup.shutdownGracefully();
                  workerGroup.shutdownGracefully();
              }
      
          }
      }
      

    客户端

    • ClientHandler

      public class ClientHandler  extends SimpleChannelInboundHandler {
          @Override
          protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
              System.out.println("服务器的ip=" + ctx.channel().remoteAddress());
              System.out.println("收到服务器消息=" + msg);
          }
      }
      
    • YzxClient

      public class YzxClient {
          public static void main(String[] args)  throws  Exception{
      
              EventLoopGroup group = new NioEventLoopGroup();
      
              try {
      
                  Bootstrap bootstrap = new Bootstrap();
                  bootstrap.group(group).channel(NioSocketChannel.class)
                          .handler(new ChannelInitializer(){
      			            @Override
      					    protected void initChannel(SocketChannel ch) throws Exception {
      					        ChannelPipeline pipeline = ch.pipeline();
      					
      					        //加入一个出站的handler 对数据进行一个编码
      					        pipeline.addFirst(new LongToByteEncoder());
      					
      					        //这时一个入站的解码器(入站handler )
      					        pipeline.addLast(new ByteToLongDecoder());
      					        //加入一个自定义的handler , 处理业务
      					        pipeline.addLast(new ClientHandler());
      
      					    }
      		            }); 
      
                  ChannelFuture channelFuture = bootstrap.connect("localhost", 7000).sync();	
                  channelFuture.channel().closeFuture().sync();	
              }finally {
                  group.shutdownGracefully();
              }
          }
      }
      

    注意事项

    • 在编码encode时,如果传入的数据类型和编解码的类型(编码器的泛型)不符,那么会忽略这个编码器
    • 在解码时,如果使用ReplayingDecoder做父类,那么可以不用判断字节数
      public class MyByteToLongDecoder extends ReplayingDecoder {
          @Override
          protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
              //在 ReplayingDecoder 不需要判断数据是否足够读取,内部会进行处理判断
              out.add(in.readLong());
          }
      }
        
         
        

      常用编解码器

      • 解码器

        • LineBasedFrameDecoder:这个类在Netty内部也有使用,它使用行尾控制字符(\n或者\r\n)作为分隔符来解析数据。
        • DelimiterBasedFrameDecoder:使用自定义的特殊字符作为消息的分隔符。
        • HttpObjectDecoder:一个HTTP数据的解码器
        • LengthFieldBasedFrameDecoder:通过指定长度来标识整包消息,这样就可以自动的处理黏包和半包消息。
      • 解码器

        • ObjectEncoder:把任意对象编码
        • SocketMessageEncoder:把Socket(比如websocket)发送的消息的消息编码
        • ZlibEncoder:把压缩文件编码
        • Bzip2Encoder:把压缩文件编码

      TCP粘包和拆包

      介绍

      • TCP是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样做虽然提高了效率,但是接收端就难于分辨出完整的数据包了,因为面向流的通信是无消息保护边界的。
      • 由于TCP无消息保护边界,需要在接收端处理消息边界问题,也就是我们所说的粘包、拆包问题。

      解决方案

      • 使用自定义协议+编解码器来解决
      • 解决服务器端每次读取数据长度的问题,这个问题解决,就不会出现服务器多读或少读数据的问题,从而避免的TCP粘包、拆包

      实现

      自定数据格式

      • MessageProtocol
        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;
            }
        }
        

      自定数据格式的编解码器

      • MessageDecoder
        public class MessageDecoder extends ReplayingDecoder {
            @Override
            protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
                //需要将得到二进制字节码-> MessageProtocol 数据包(对象)
                int length = in.readInt();
        
                byte[] content = new byte[length];
                in.readBytes(content);
        
                //封装成 MessageProtocol 对象,放入 out, 传递下一个handler业务处理
                MessageProtocol messageProtocol = new MessageProtocol();
                messageProtocol.setLen(length);
                messageProtocol.setContent(content);
        
                out.add(messageProtocol);	
            }
        }
          
           
      • MessageEncoder
        public class MessageEncoder extends MessageToByteEncoder {
            @Override
            protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
                out.writeInt(msg.getLen());
                out.writeBytes(msg.getContent());
            }
        }
        
      • 服务端

        • ServerHandler
          public class ServerHandler extends SimpleChannelInboundHandler{
              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.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();
                  int responseLen = responseContent.getBytes("utf-8").length;
                  byte[]  responseContent2 = responseContent.getBytes("utf-8");
                  //构建一个协议包
                  MessageProtocol messageProtocol = new MessageProtocol();
                  messageProtocol.setLen(responseLen);
                  messageProtocol.setContent(responseContent2);
          
                  ctx.writeAndFlush(messageProtocol);	
              }
          }
          
        • YzxServer
          public class YzxServer {
              public static void main(String[] args) throws Exception{
          
                  EventLoopGroup bossGroup = new NioEventLoopGroup(1);
                  EventLoopGroup workerGroup = new NioEventLoopGroup();
          
                  try {
          
                      ServerBootstrap serverBootstrap = new ServerBootstrap();
                      serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer(){
          			            @Override
          					    protected void initChannel(SocketChannel ch) throws Exception {
          					        ChannelPipeline pipeline = ch.pipeline();
          
          					        pipeline.addFirst(new MessageDecoder());//解码器
          					        pipeline.addLast(new MessageEncoder());//编码器
          					        pipeline.addLast(new ServerHandler());
          					    }
          		            }); 
          
          
          
                      ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
                      channelFuture.channel().closeFuture().sync();
          
                  }finally {
                      bossGroup.shutdownGracefully();
                      workerGroup.shutdownGracefully();
                  }
          
              }
          }
          

        客户端

        • ClientHandler

          public class ClientHandler extends SimpleChannelInboundHandler {
          
              private int count;
          
          	@Override
              public void channelActive(ChannelHandlerContext ctx) throws Exception {
                  //连接上时向服务端发送几条数据 
                  for(int i = 0; i< 5; i++) {
                      String mes = "hello server";
                      byte[] content = mes.getBytes(Charset.forName("utf-8"));
                      int length = mes.getBytes(Charset.forName("utf-8")).length;
          
                      //创建协议包对象
                      MessageProtocol messageProtocol = new MessageProtocol();
                      messageProtocol.setLen(length);
                      messageProtocol.setContent(content);
                      ctx.writeAndFlush(messageProtocol);	
                  }
          
              }
          
              @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 exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                  System.out.println("异常消息=" + cause.getMessage());
                  ctx.close();
              }
          }
          
        • YzxClient

          public class YzxClient {
              public static void main(String[] args)  throws  Exception{
          
                  EventLoopGroup group = new NioEventLoopGroup();
          
                  try {
          
                      Bootstrap bootstrap = new Bootstrap();
                      bootstrap.group(group).channel(NioSocketChannel.class)
                              .handler(new ChannelInitializer(){
          			            @Override
          					    protected void initChannel(SocketChannel ch) throws Exception {
          					        ChannelPipeline pipeline = ch.pipeline();
          					        pipeline.addFirst(new MessageEncoder()); //加入编码器
          					        
          					        pipeline.add(new MessageDecoder()); //加入解码器
          					        pipeline.addLast(new ClientHandler());
          					    }
          		            }); 
          
                      ChannelFuture channelFuture = bootstrap.connect("localhost", 7000).sync();
          
                      channelFuture.channel().closeFuture().sync();
          
                  }finally {
                      group.shutdownGracefully();
                  }
              }
          }
          

        你可能感兴趣的:(NIO,源码,其他,java,netty)