前言
继上次文章后小编很久都没有发布文章了,已经一个多月了,小编还是要努力更新的,最近确实比较忙,并且台风烟花也过来了,愿各地灾情早点过去吧!好了话不多说,今天继续我们的netty的编解码机制。在编解码之前,我们先说一下netty的ByteBuf,以及tcp的粘包和拆包。
netty核心组件之ByteBuf
上篇文章中Netty框架之核心组件,主要讲了Netty Channel,ChannelPipeline等重要组件,但是小编忘记了一个、那就是ByteBuf。那小编首先带大家了解一下netty的ByteBuf。讲到ByteBuf大家是否还有印象nio中的ByteBuf。不过netty的ByteBuf不是在nio的ByteBuf上进行封装(这话有点饶),而是重新定义了一个ByteBuf。先看下图来了解一下吧:
小编简单说明: 特性: 1、和nio的bytebuf不一样,他有读写双索引,不像nio的只有一个索引position,读和写都是往前,写好还需要flip进行读取。这样操作跟简单。 2、手动释放回收。 3、复制视图,和源ByteBuf共享一份数据,但是对视图的读写索引进行单独操作不会对源ByteBuf的索引进行改变。 4、自动扩容,相对于nio的ByteBuf一旦确认大小不可修改外,他可以自动扩容,但是不可以超过最大容量。 结构: 1、维护读写索引,默认为0,写数据的时候,写索引往前加,读数据一样往前加,读的区域为写的索引,且判断可读只需要readerIndex < writerIndex。并且可读区域也很明显。如果读取的索引大于写的索引则就会报错。 2、当writerIndex到达一开始设置的capacity时,则会自动扩容,但是扩容不会超过max capacity。
下面小编通过代码来演示一下ByteBuf:
public class ByteBufTest {
@Test
public void byteBufRwTest() {
int initialCapacity = 5;
int maxCapacity = 100;
ByteBuf buffer = Unpooled.buffer(initialCapacity, maxCapacity);
buffer.writeByte(1);
buffer.writeByte(2);
buffer.readByte();
buffer.readByte();
try {
buffer.readByte();
} catch (Exception e) {
e.printStackTrace();
}
buffer.writeByte(3);
buffer.writeByte(4);
buffer.writeByte(5);
buffer.discardReadBytes();
buffer.writeByte(6);
System.out.println(buffer.capacity());
}
@Test
public void copyTest(){
ByteBuf buffer = Unpooled.wrappedBuffer(new byte[]{1,2,3,4,5});
ByteBuf duplicate = buffer.duplicate();
duplicate.readByte();
duplicate.setByte(4,6);
ByteBuf slice = buffer.slice();
ByteBuf slice2 = buffer.slice(1,4);
ByteBuf slice3 = buffer.readSlice(1);
ByteBuf copy = buffer.copy();
}
}
好了ByteBuf大家知道怎么用了并且了解其特性与数据结构即可,下面小编再来聊聊TCP的拆包和粘包。
TCP拆包与粘包
讲到TCP拆包和粘包问题,也是面试中比较常见的问题,咱们先来看下面这张图:
小编先给大家解释一下上图: 1、红色是一个消息,绿色是一个消息,但是这tcp流式传输的时候,是没有边界的,也就是说他区分不了两个消息,他只会不停的往缓存区里面写,应用(解码处理器)不停的往里面拿数据。 2、粘包即上面在传输过程中红色和绿色在一起,到了缓冲区则需要拆包,即解码处理的时候只读了绿色的消息块。这与udp不一样udp是一个消息一个消息的传输。当然返回来也一样,如果一个消息太大,那tcp传输的过程中就会将消息拆包。
下面小编模拟一下拆包和粘包的过程: 服务端代码如下
public class PacketSplicingTest {
private ServerBootstrap serverBootstrap;
@Before
public void initSocketServer() {
serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup(5));
serverBootstrap.channel(NioServerSocketChannel.class);
}
@Test
public void splicingTest() throws InterruptedException {
serverBootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new TrackHandler());
}
});
ChannelFuture sync = serverBootstrap.bind(8080).sync();
sync.channel().closeFuture().sync();
}
private class TrackHandler extends SimpleChannelInboundHandler {
int count = 0;
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
String message = byteBuf.toString(Charset.defaultCharset());
System.out.println(String.format("message%s:%s", ++count, message));
}
}
}
测试 控制台: 这里很直观的看到粘包和拆包的结果,当然这里小编设置了发送的缓存区和发送的大小。那假如消息出现这样的情况,那我们怎么来解决粘包和拆包的问题呢。
拆包粘包的解决方案
解决粘包拆包的问题,其实最主要的是约定客户端的发送消息,以及服务端解析消息的规则。这样即可,看起来很简单对吧。 1、固定长度:最简单的方式。消息端发送消息发送固定长度,不能大于或小于这个长度,服务端就读取固定长度,这种场景一般是心跳保活场景。假设消息长度不固定则不适用了。 2、消息分割:特殊字符的分割,比方说换行符号拆分。 3、请求头,标示大小:消息分割场景太过单一,万一需要用到特殊字符的时候则很难区分,那么就会产生自定义协议,目前websocket协议,dubbo协议以及http协议都是采用此方法。
小结:
这篇文章出炉的时候和写的时候又过去一个多星期,从郑州的洪水到浙江的台风到现在南京的新冠疫情又严重了,目前是奥运会看得小编很气愤。 上面是题外话啊,下面几篇文章都是会对netty框架的应用,这篇文章比较简单,大家继续打好基础,之后将会是大量的应用场景,包括了http的简易协议怎么处理的,包括dubbo协议等等。让你从网络架构的高层俯瞰如何定义,以及一系列应用场景的编码。小编还是继续努力,不偷懒一直学习下去。
|