netty系列之:基于流的数据传输
简介
我们知道由两种数据的传输方式,分别是字符流和字节流,字符流的意思是传输的对象就是字符串,格式已经被设置好了,发送方和接收方按照特定的格式去读取就行了,而字节流是指将数据作为最原始的二进制字节来进行传输。
今天给大家介绍一下在netty中的基于流的数据传输。
package和byte
熟悉TCP/IP协议的同学应该知道,在TCP/IP中,因为底层协议有支持的数据包的最大值,所以对于大数据传输来说,需要对数据进行拆分和封包处理,并将这些拆分组装过的包进行发送,最后在接收方对这些包进行组合。在各个包中有固定的结构,所以接收方可以很清楚的知道到底应该组合多少个包作为最终的结果。
那么对于netty来说,channel中传输的是ByteBuf,实际上最最最底层的就是byte数组。对于这种byte数组来说,接收方并不知道到底应该组合多少个byte来合成原来的消息,所以需要在接收端对收到的byte进行组合,从而生成最终的数据。
那么对于netty中的byte数据流应该怎么组合呢?我们接下来看两种组合方法。
手动组合
这种组合的方式的基本思路是构造一个目标大小的ByteBuf,然后将接收到的byte通过调用ByteBuf的writeBytes方法写入到ByteBuf中。最后从ByteBuf中读取对应的数据。
比如我们想从服务端发送一个int数字给客户端,一般来说int是32bits,然后一个byte是8bits,那么一个int就需要4个bytes组成。
在server端,可以建立一个byte的数组,数组中包含4个元素。将4个元素的byte发送给客户端,那么客户端该如何处理呢?
首先我们需要建立一个clientHander,这个handler应该继承ChannelInboundHandlerAdapter,并且在其handler被添加到ChannelPipeline的时候初始化一个包含4个byte的byteBuf。
handler被添加的时候会触发一个handlerAdded事件,所以我们可以这样写:
private ByteBuf buf;
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
//创建一个4个byte的缓冲器
buf = ctx.alloc().buffer(4);
}
上例中,我们从ctx分配了一个4个字节的缓冲器,并将其赋值给handler中的私有变量buf。
当handler执行完毕,从ChannelPipeline中删除的时候,会触发handlerRemoved事件,在这个事件中,我们可以对分配的Bytebuf进行清理,通常来说,可以调用其release方法,如下所示:
public void handlerRemoved(ChannelHandlerContext ctx) {
buf.release(); // 释放buf
buf = null;
}
然后最关键的一步就是从channel中读取byte并将其放到4个字节的byteBuf中。在之前的文章中我们提到了,可以在channelRead方法中,处理消息读取的逻辑。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf m = (ByteBuf) msg;
buf.writeBytes(m); // 写入一个byte
m.release();
if (buf.readableBytes() >= 4) { // 已经凑够4个byte,将4个byte组合称为一个int
long result = buf.readUnsignedInt();
ctx.close();
}
}
每次触发channelRead方法,都会将读取到的一个字节的byte通过调用writeBytes方法写入buf中。当buf的可读byte大于等于4个的时候就说明4个字节已经读满了,可以对其进行操作了。
这里我们将4个字节组合成一个unsignedInt,并使用readUnsignedInt方法从buf中读取出来组合称为一个int数字。
上面的例子虽然可以解决4个字节的byte问题,但是如果数据结构再负责一点,上面的方式就会力不从心,需要考虑太多的数据组合问题。接下来我们看另外一种方式。
Byte的转换类
netty提供了一个ByteToMessageDecoder的转换类,可以方便的对Byte转换为其他的类型。
我们只需要重新其中的decode方法,就可以实现对ByteBuf的转换:
public class SquareDecoder extends ByteToMessageDecoder {
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
out.add(in.readBytes(in.readableBytes()));
}
}
上面的例子将byte从input转换到output中,当然,你还可以在上面的方法中进行格式转换,如下所示:
public class TimeDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (in.readableBytes() < 4) {
return;
}
out.add(in.readBytes(4));
}
}
上面的例子会先判断in中是否有4个byte,如果有就将其读出来放到out中去。那么有同学会问了,输入不是一个byte一个byte来的吗?为什么这里可以一次读取到4个byte?这是因为ByteToMessageDecoder内置了一个缓存装置,所以这里的in实际上是一个缓存集合。
ReplayingDecoder
netty还提供了一个更简单的转换ReplayingDecoder,如果使用ReplayingDecoder重新上面的逻辑就是这样的:
public class TimeDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(
ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
out.add(in.readBytes(4));
}
}
只需要一行代码即可。
事实上ReplayingDecoder 是ByteToMessageDecoder 的子类,是在ByteToMessageDecoder上丰富了一些功能的结果。
他们两的区别在于ByteToMessageDecoder 还需要通过调用readableBytes来判断是否有足够的可以读byte,而使用ReplayingDecoder直接读取即可,它假设的是所有的bytes都已经接受成功了。
比如下面使用ByteToMessageDecoder的代码:
public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx,
ByteBuf buf, List<Object> out) throws Exception {
if (buf.readableBytes() < 4) {
return;
}
buf.markReaderIndex();
int length = buf.readInt();
if (buf.readableBytes() < length) {
buf.resetReaderIndex();
return;
}
out.add(buf.readBytes(length));
}
}
上例假设在byte的头部是一个int大小的数组,代表着byte数组的长度,需要先读取int值,然后再根据int值来读取对应的byte数据。
和下面的代码是等价的:
public class IntegerHeaderFrameDecoder
extends ReplayingDecoder<Void> {
protected void decode(ChannelHandlerContext ctx,
ByteBuf buf, List<Object> out) throws Exception {
out.add(buf.readBytes(buf.readInt()));
}
}
上面代码少了判断的步骤。
那么这是怎么实现的呢?
事实上ReplayingDecoder 会传递一个会抛出 Error的 ByteBuf , 当 ByteBuf 读取的byte个数不满足要求的时候,会抛出异常,当ReplayingDecoder 捕获到这个异常之后,会重置buffer的readerIndex到最初的状态,然后等待后续的数据进来,然后再次调用decode方法。
所以,ReplayingDecoder的效率会比较低,为了解决这个问题,netty提供了checkpoint() 方法。这是一个保存点,当报错的时候,可以不会退到最初的状态,而是回退到checkpoint() 调用时候保存的状态,从而可以减少不必要的浪费。
总结
本文介绍了在netty中进行stream操作和变换的几种方式,希望大家能够喜欢。
本文已收录于 http://www.flydean.com/07-netty-stream-based-transport/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
netty系列之:基于流的数据传输的更多相关文章
- Netty系列(四)TCP拆包和粘包
Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...
- 【读后感】Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ?
[读后感]Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ? 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商 ...
- Netty 系列之 Netty 高性能之道 高性能的三个主题 Netty使得开发者能够轻松地接受大量打开的套接字 Java 序列化
Netty系列之Netty高性能之道 https://www.infoq.cn/article/netty-high-performance 李林锋 2014 年 5 月 29 日 话题:性能调优语言 ...
- Netty 系列之 Netty 高性能之道
1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用 Netty4 + Thrift 压缩二进制编解码技术,他们实现了 10 W TPS(1 K 的复杂 POJO 对象)的跨 ...
- Netty系列之Netty高性能之道
转载自http://www.infoq.com/cn/articles/netty-high-performance 1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Ne ...
- 转:Netty系列之Netty高性能之道
1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用 ...
- Dealing with a Stream-based Transport 处理一个基于流的传输 粘包 即使关闭nagle算法,也不能解决粘包问题
即使关闭nagle算法,也不能解决粘包问题 https://waylau.com/netty-4-user-guide/Getting%20Started/Dealing%20with%20a%20S ...
- 2. 彤哥说netty系列之IO的五种模型
你好,我是彤哥,本篇是netty系列的第二篇. 欢迎来我的公从号彤哥读源码系统地学习源码&架构的知识. 简介 本文将介绍linux中的五种IO模型,同时也会介绍阻塞/非阻塞与同步/异步的区别. ...
- 3. 彤哥说netty系列之Java BIO NIO AIO进化史
你好,我是彤哥,本篇是netty系列的第三篇. 欢迎来我的公从号彤哥读源码系统地学习源码&架构的知识. 简介 上一章我们介绍了IO的五种模型,实际上Java只支持其中的三种,即BIO/NIO/ ...
随机推荐
- 20201123 实验一《Python程序设计》实验报告
20201123 2020-2021-2 <Python程序设计>实验一报告 课程:<Python程序设计> 班级:2011班 姓名:晏鹏捷 学号:20201123 实验教师: ...
- 32、sed命令详解
32.1.sed介绍: 1.sed(sed软件常称做)是流编辑器,是操作.过滤.和转换文本内容的工具: 2.sed的模式空间和保持空间介绍: (1)模式空间:sed处理文本内容行的一个临时缓冲区,模式 ...
- Gym 101308D Database 枚举
大致题意: 给出一张表,n行m列,每一行的列用逗号分隔.判断这个表是否有冗余元素.如果一张表中有两行两列对应的的元素相同,那么这个表就有冗余元素. 分析: 先枚举要排序的列,然后枚举行,如果相邻两行相 ...
- bcprov-jdk15-145.rar
javapdf文件操作,加密包 bcprov-jdk15-145.rar https://files.cnblogs.com/files/blogs/692137/bcprov-jdk15-145.r ...
- centos Gitlab AD 集成
简述 Gitlab支持集成LDAP用户认证系统.兼容包括Microsoft Active Directory, Apple Open Directory, Open LDAP, 与389 Server ...
- JAVA并发(8)-ThreadPoolExecutor的讲解
很久前(2020-10-23),就有想法学习线程池并输出博客,但是写着写着感觉看不懂了,就不了了之了.现在重拾起,重新写一下(学习一下). 线程池的优点也是老生常谈的东西了 减少线程创建的开销(任务数 ...
- Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ
解决办法: 去掉ps -aux 中的"-",改成ps aux 就可以了
- chrome 屏蔽广告的利器
Adblock Plus https://chrome.google.com/webstore/detail/adblock-plus/cfhdojbkjhnklbpkdaibdccddilifddb ...
- linux挂载光驱
挂载光驱到linux中.linux的镜像盘中有安装oracle的所有的软件包,可以会用yum一键安装. 1.此时的linux的界面显示光驱图标 2.挂载 因为光盘里面的文件是只读模式的,yum安装时不 ...
- 学C记录(理解递归问题之汉诺塔)
汉诺游戏规则如下: 1.有三根相邻的柱子,标号为A,B,C. 2.A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘. 3.现在把所有盘子一个一个移动到柱子B上,并且每次移动同一根柱子上都不能出现大盘 ...