前言
继上次文章后小编很久都没有发布文章了,已经一个多月了,小编还是要努力更新的,最近确实比较忙,并且台风烟花也过来了,愿各地灾情早点过去吧!好了话不多说,今天继续我们的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);
//回收缓存空间,并且将读索引返回到0,即释放读过的数据
//并且将345上移到012的索引位置,写索引变成3,则下面的写入扩容不会进行
//并且不会改变原来写入的数据即3和4的索引位置还是4和5
buffer.discardReadBytes();
//不进行上面回收操作,则进行扩容 扩容后为64
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();
//操作视图的读索引不会影响到源buffer
duplicate.readByte();
//设置会改变原buffer的值
duplicate.setByte(4,6);
//复制可读的缓冲区域
ByteBuf slice = buffer.slice();
//复制可读范围索引,范围超出会报错
ByteBuf slice2 = buffer.slice(1,4);
//复制一个值并且移动了原来buffer读索引+1
ByteBuf slice3 = buffer.readSlice(1);
//完全copy了一份新的byteBuf
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协议等等。让你从网络架构的高层俯瞰如何定义,以及一系列应用场景的编码。小编还是继续努力,不偷懒一直学习下去。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13549.html