一、Netty粘包和拆包解决方案
Netty提供了多个解码器,可以进行分包的操作,分别是:
* LineBasedFrameDecoder (换行)
LineBasedFrameDecoder是回车换行解码器,如果用户发送的消息以回车换行符作为消息结束的标识,则可以直接使用Netty的LineBasedFrameDecoder对消息进行解码,只需要在初始化Netty服务端或者客户端时将LineBasedFrameDecoder正确的添加到ChannelPipeline中即可,不需要自己重新实现一套换行解码器。
LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,判断看是否有“\n”或者“\r\n”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。防止由于数据报没有携带换行符导致接收到ByteBuf无限制积压,引起系统内存溢出。
* DelimiterBasedFrameDecoder(添加特殊分隔符报文来分包)
DelimiterBasedFrameDecoder是分隔符解码器,用户可以指定消息结束的分隔符,它可以自动完成以分隔符作为码流结束标识的消息的解码。
回车换行解码器实际上是一种特殊的DelimiterBasedFrameDecoder解码器。
* FixedLengthFrameDecoder(使用定长的报文来分包)
FixedLengthFrameDecoder是固定长度解码器,它能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP的粘包/拆包等问题,非常实用。
对于定长消息,如果消息实际长度小于定长,则往往会进行补位操作,它在一定程度上导致了空间和资源的浪费。但是它的优点也是非常明显的,编解码比较简单,因此在实际项目中仍然有一定的应用场景。
* LengthFieldBasedFrameDecoder (自定义解码器跟编码器)
本文介绍的重点LengthFieldBasedFrameDecoder,一般包含了消息头(head)、消息体(body):消息头是固定的长度,一般有有以下信息 -> 是否压缩(zip)、消息类型(type or cmdid)、消息体长度(body length);消息体长度不是固定的,其大小由消息头记载,一般记载业务交互信息。
netty对应来说就是编码器(Encoder)跟解码器(Decoder),一般其中会有一个基本消息类对外输出
二、实例演示
首先编写自定义协议类:
package com.spring.netty.handler2; /** * 自定义Person协议 */ public class PersonProtocol { 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; } }
新建服务端代码:
package com.spring.netty.handler2; 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 Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class). childHandler(new MyServerInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8899).sync(); channelFuture.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
package com.spring.netty.handler2; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; public class MyServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new MyPersonDecoder()); pipeline.addLast(new MyPersonEncoder()); pipeline.addLast(new MyServerHandler()); } }
package com.spring.netty.handler2; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ReplayingDecoder; import java.util.List; /** * Person解码器 */ 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); PersonProtocol personProtocol = new PersonProtocol(); personProtocol.setLength(length); personProtocol.setContent(content); out.add(personProtocol); } }
package com.spring.netty.handler2; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; /** * 编码器 */ public class MyPersonEncoder extends MessageToByteEncoder<PersonProtocol> { @Override protected void encode(ChannelHandlerContext ctx, PersonProtocol msg, ByteBuf out) throws Exception { System.out.println("MyPersonEncoder encode invoked!"); out.writeInt(msg.getLength()); out.writeBytes(msg.getContent()); } }
package com.spring.netty.handler2; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import java.nio.charset.Charset; import java.util.UUID; public class MyServerHandler extends SimpleChannelInboundHandler<PersonProtocol> { private int count; @Override protected void channelRead0(ChannelHandlerContext ctx, PersonProtocol 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)); //服务端向客户端返回uuid String responseMessage = UUID.randomUUID().toString(); int responseLength = responseMessage.getBytes("utf-8").length; byte[] responseContent = responseMessage.getBytes("utf-8"); PersonProtocol personProtocol = new PersonProtocol(); personProtocol.setLength(length); personProtocol.setContent(content); ctx.writeAndFlush(responseContent); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
编写客户端程序:
package com.spring.netty.handler2; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; public class MyClient { public static void main(String[] args) throws Exception { EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new MyPersonDecoder()); pipeline.addLast(new MyPersonEncoder()); pipeline.addLast(new MyClientHandler()); } }); ChannelFuture channelFuture = bootstrap.connect("localhost",8899).sync(); channelFuture.channel().closeFuture().sync(); }finally { eventLoopGroup.shutdownGracefully(); } } }
package com.spring.netty.handler2; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import java.nio.charset.Charset; public class MyClientHandler extends SimpleChannelInboundHandler<PersonProtocol> { private int count; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for(int i=0;i<10;++i){ String messageToBeSent = "sent from client"; byte[] content = messageToBeSent.getBytes(Charset.forName("utf-8")); int length = messageToBeSent.getBytes(Charset.forName("utf-8")).length; PersonProtocol personProtocol = new PersonProtocol(); personProtocol.setLength(length); personProtocol.setContent(content); ctx.writeAndFlush(personProtocol); } } //从服务器端接收数据 @Override protected void channelRead0(ChannelHandlerContext ctx, PersonProtocol 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(); } }
分别启动服务端和客户端查看效果:
服务端效果如图:
客户端效果如图: