Netty 编解码器网络传输的单位是字节因此就需要使用编码器将应用程序的数据转换为字节使用解码器将字节转换为应用程序的数据。Netty 提供了一系列实用的编码器、解码器以及编解码器。编解码器的本质是一个 Handler均实现了 ChannelInboundHadnler / ChannelOutboundHandler 接口并重写了 channelRead 方法。对于每个从入站 / 出站 Channel 读取的消息解码 / 编码器都会调用自身的 decode() / encode() 方法进行解码 / 编码并将解码 / 编码结果保存到 List 中传递给 ChannelPipeline 的下一个 ChannelInboundHandler / ChannelOutboundHandler 进行处理Long 类型消息编解码示例编写一个 Byte 类型转 Long 类型的解码器importio.netty.buffer.ByteBuf;importio.netty.channel.ChannelHandlerContext;importio.netty.handler.codec.ByteToMessageDecoder;importjava.util.List;publicclassByteToLongDecoderextendsByteToMessageDecoder{/** * decode会根据接收的数据被调用多次,直到没有新的元素被添加到List或ByteBuf没有可读字节为止 * 每次解码完成后若List不为空则会将List传递给下一个ChannelInboundHandler处理(该处理器也会被调用多次) * * param ctx 处理器上下文 * param in 入站的消息数据(ByteBuf) * param out 解码后的数据集合 * throws Exception 处理时异常 */Overrideprotectedvoiddecode(ChannelHandlerContextctx,ByteBufin,ListObjectout)throwsException{System.out.println(ByteToLongDecoder被调用);// 因为Long是8个字节,所以可读字节数至少为8才能读取到一个Longif(in.readableBytes()8){out.add(in.readLong());}}}除了继承 ByteToMessageDecoder还可以继承 ReplayingDecoder。ReplayingDecoder 继承并拓展了 ByteToMessageDecoder 类其泛型参数 S 指定了用户状态管理的类型Void 表示不需要状态管理。使用此类后不必再在 decode() 方法中调用 readableBytes() 方法判断可读字节数publicclassByteToLongDecoderextendsReplayingDecoderVoid{Overrideprotectedvoiddecode(ChannelHandlerContextctx,ByteBufin,ListObjectout)throwsException{System.out.println(ByteToLongDecoder被调用);// ReplayingDecoder不需要判断数据是否足够读取(内部会进行处理判断)out.add(in.readLong());}}ReplayingDecoder 使用方便但也有一些局限性并不是所有的 ByteBuf 操作都被支持。如果调用了一个不被支持的方法将会抛出 UnsupportedOperationException。ReplayingDecoder 在某些情况下可能稍慢于 ByteToMessageDecoder。例如网络缓慢并且消息格式复杂时消息会被拆成了多个碎片速度变慢。编写一个 Long 类型转 Byte 类型的编码器importio.netty.buffer.ByteBuf;importio.netty.channel.ChannelHandlerContext;importio.netty.handler.codec.MessageToByteEncoder;publicclassLongToByteEncoderextendsMessageToByteEncoderLong{Overrideprotectedvoidencode(ChannelHandlerContextctx,Longmsg,ByteBufout)throwsException{System.out.println(LongToByteEncoder被调用,被编码的数据:msg);out.writeLong(msg);}}编写服务端 Handler 用于处理客户端发来的消息数据Long 类型importio.netty.channel.ChannelHandlerContext;importio.netty.channel.SimpleChannelInboundHandler;publicclassServerHandlerextendsSimpleChannelInboundHandlerLong{OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,Longmsg)throwsException{System.out.println(收到客户端消息:msg);// 给客户端回复一个Longctx.writeAndFlush(54321L);}OverridepublicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause)throwsException{cause.printStackTrace();ctx.close();}}编写服务端并使其监听在 7000 端口importio.netty.bootstrap.ServerBootstrap;importio.netty.channel.ChannelFuture;importio.netty.channel.ChannelInitializer;importio.netty.channel.ChannelPipeline;importio.netty.channel.EventLoopGroup;importio.netty.channel.nio.NioEventLoopGroup;importio.netty.channel.socket.SocketChannel;importio.netty.channel.socket.nio.NioServerSocketChannel;publicclassMyServer{publicstaticvoidmain(String[]args)throwsException{EventLoopGroupbossGroupnewNioEventLoopGroup(1);EventLoopGroupworkerGroupnewNioEventLoopGroup();try{ServerBootstrapserverBootstrapnewServerBootstrap();serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(newChannelInitializerSocketChannel(){OverrideprotectedvoidinitChannel(SocketChannelch)throwsException{ChannelPipelinepipelinech.pipeline();// 添加解码器pipeline.addLast(newByteToLongDecoder());// 添加编码器pipeline.addLast(newLongToByteEncoder());pipeline.addLast(newServerHandler());}});ChannelFuturechannelFutureserverBootstrap.bind(7000).sync();channelFuture.channel().closeFuture().sync();}finally{bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}}编写客户端 Handler 用于向服务端发送 Long 类型消息并接受回复的 Long 类型消息importio.netty.channel.ChannelHandlerContext;importio.netty.channel.SimpleChannelInboundHandler;publicclassClientHandlerextendsSimpleChannelInboundHandlerLong{OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,Longmsg)throwsException{System.out.println(收到服务端消息:msg);}OverridepublicvoidchannelActive(ChannelHandlerContextctx)throwsException{System.out.println(客户端发送数据);// 向服务端发送一个Longctx.writeAndFlush(12345L);}}编写客户端使其连接上服务端importio.netty.bootstrap.Bootstrap;importio.netty.channel.ChannelFuture;importio.netty.channel.ChannelInitializer;importio.netty.channel.ChannelPipeline;importio.netty.channel.EventLoopGroup;importio.netty.channel.nio.NioEventLoopGroup;importio.netty.channel.socket.SocketChannel;importio.netty.channel.socket.nio.NioSocketChannel;publicclassMyClient{publicstaticvoidmain(String[]args)throwsException{EventLoopGroupgroupnewNioEventLoopGroup();try{BootstrapbootstrapnewBootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(newChannelInitializerSocketChannel(){OverrideprotectedvoidinitChannel(SocketChannelch)throwsException{ChannelPipelinepipelinech.pipeline();// 添加编码器pipeline.addLast(newLongToByteEncoder());// 添加解码器pipeline.addLast(newByteToLongDecoder());pipeline.addLast(newClientHandler());}});ChannelFuturechannelFuturebootstrap.connect(localhost,7000).sync();channelFuture.channel().closeFuture().sync();}finally{group.shutdownGracefully();}}}先后启动服务端和客户端并观察运行结果服务端运行结果ByteToLongDecoder被调用 收到客户端消息:12345 LongToByteEncoder被调用,被编码的数据:54321客户端运行结果客户端发送数据 LongToByteEncoder被调用,被编码的数据:12345 ByteToLongDecoder被调用 收到服务端消息:54321若客户端发送的消息不为 Long publicclassClientHandlerextendsSimpleChannelInboundHandlerLong{OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,Longmsg)throwsException{System.out.println(收到服务端消息:msg);}OverridepublicvoidchannelActive(ChannelHandlerContextctx)throwsException{System.out.println(客户端发送数据);ctx.writeAndFlush(Unpooled.copiedBuffer(1234567887654321,CharsetUtil.UTF_8));}}先后启动服务端和客户端并观察运行结果服务端运行结果ByteToLongDecoder被调用 收到客户端消息:3544952156018063160 LongToByteEncoder被调用,被编码的数据:54321 ByteToLongDecoder被调用 收到客户端消息:4050765991979987505 LongToByteEncoder被调用,被编码的数据:54321客户端运行结果客户端发送数据 ByteToLongDecoder被调用 收到服务端消息:54321 ByteToLongDecoder被调用 收到服务端消息:54321原因分析由 MessageToByteEncoder 类的 write 方法源码可知若发送的消息类型为 T 则调用 encode 方法进行编码否则将直接发送。而客户端发送的 “1234567887654321” 为 16 个字节且不为指定的类型 Long 因此会直接发送到服务端。服务端将 16 个字节拆分为了 2 个 Long 进行处理。其他编码器类名描述LineBasedFrameDecoder这个类在 Netty 内部也有使用它使用行尾控制字符\n 或者 \r\n 作为分隔符来解析数据。DelimiterBasedFrameDecoder使用自定义的特殊字符作为消息的分隔符。HttpObjectDecoder一个 HTTP 数据的解码器。LengthFieldBasedFrameDecoder通过指定长度来标识整包消息从而可以自动处理粘包和拆包消息。