前言
学习 Netty 也有一段时间了, Netty 作为一个高性能的异步框架, 很多 RPC 框架也运用到了 Netty 中的知识, 在 rpc 框架中丰富的数据协议及编解码可以让使用者更加青睐;
Netty 支持丰富的编解码框架, 其本身内部提供的编解码也可以应对各种业务场景;
今天主要就是学习下 Netty 中提供的编, 解码类, 之前只是简单的使用了下 Netty 提供的解码类, 今天更加深入的研究下 Netty 中编, 解码的源码及部分使用.
编, 解码的概念
编码(Encoder)
编码就是将我们发送的数据编码成字节数组方便在网络中进行传输, 类似 Java 中的序列化, 将对象序列化成字节传输
解码(Decoder)
解码和编码相反, 将传输过来的字节数组转化为各种对象来进行展示等, 类似 Java 中的反序列化
如:
- // 将字节数组转化为字符串
- new String(byte bytes[], Charset charset)
编, 解码超类
ByteToMessageDecoder: 解码超类, 将字节转换成消息
解码解码一般用于将获取到的消息解码成系统可识别且自己需要的数据结构; 因此 ByteToMessageDecoder 需要继承 ChannelInboundHandlerAdapter 入站适配器来获取到入站的数据, 在 handler 使用之前通过 channelRead 获取入站数据进行一波解码;
ByteToMessageDecoder 类图
源码分析
通过 channelRead 获取入站数据, 将数据缓存至 cumulation 数据缓冲区, 最后在传给 decode 进行解码, 在 read 完成之后清空缓存的数据
1. 获取入站数据
- /**
- * 通过重写 channelRead 方法来获取入站数据
- */
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- // 检测是否是 byteBuf 对象格式数据
- if (msg instanceof ByteBuf) {
- // 实例化字节解码成功输出集合 即 List<Object> out
- CodecOutputList out = CodecOutputList.newInstance();
- try {
- // 获取到的请求的数据
- ByteBuf data = (ByteBuf) msg;
- // 如果缓冲数据区为空则代表是首次触发 read 方法
- first = cumulation == null;
- if (first) {
- // 如果是第一次 read 则当前 msg 数据为缓冲数据
- cumulation = data;
- } else {
- // 如果不是则触发累加, 将缓冲区的旧数据和新获取到的数据通过 expandCumulation 方法累加在一起存入缓冲区 cumulation
- // cumulator 累加类, 将缓冲池中数据和新数据进行组合在一起
- // private Cumulator cumulator = MERGE_CUMULATOR;
- cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
- }
- // 将缓冲区数据 cumulation 进行解码
- callDecode(ctx, cumulation, out);
- } catch (DecoderException e) {
- throw e;
- } catch (Throwable t) {
- throw new DecoderException(t);
- } finally {
- // 在解码完毕后释放引用和清空全局字节缓冲区
- if (cumulation != null && !cumulation.isReadable()) {
- numReads = 0;
- cumulation.release();
- cumulation = null;
- // discardAfterReads 为 netty 中设置的读取多少次后开始丢弃字节 默认值 16
- // 可通过 setDiscardAfterReads(int n)来设置值不设置默认 16 次
- } else if (++ numReads>= discardAfterReads) {
- // We did enough reads already try to discard some bytes so we not risk to see a OOME.
- // 在我们读取了足够的数据可以尝试丢弃一些字节已保证不出现内存溢出的异常
- //
- // See https://github.com/netty/netty/issues/4275
- // 读取次数重置为 0
- numReads = 0;
- // 重置读写指针或丢弃部分已读取的字节
- discardSomeReadBytes();
- }
- // out 为解码成功的传递给下一个 handler
- int size = out.size();
- decodeWasNull = !out.insertSinceRecycled();
- // 结束当前 read 传递到下个 ChannelHandler
- fireChannelRead(ctx, out, size);
- // 回收响应集合 将 insertSinceRecycled 设置为 false;
- // insertSinceRecycled 用于 channelReadComplete 判断使用
- out.recycle();
- }
- } else {
- // 不是的话直接 fire 传递给下一个 handler
- ctx.fireChannelRead(msg);
- }
- }
2. 初始化字节缓冲区计算器: Cumulator 主要用于全局字节缓冲区和新读取的字节缓冲区组合在一起扩容
- public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
- /**
- * alloc ChannelHandlerContext 分配的字节缓冲区
- * cumulation 当前 ByteToMessageDecoder 类全局的字节缓冲区
- * in 入站的字节缓冲区
- **/
- @Override
- public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
- final ByteBuf buffer;
- // 如果全局 ByteBuf 写入的字节 + 当前入站的字节数据大于全局缓冲区最大的容量或者全局缓冲区的引用数大于 1 个或全局缓冲区只读
- if (cumulation.writerIndex()> cumulation.maxCapacity() - in.readableBytes()
- || cumulation.refCnt()> 1 || cumulation.isReadOnly()) {
- // Expand cumulation (by replace it) when either there is not more room in the buffer
- // or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
- // duplicate().retain() or if its read-only.
- //
- // See:
- // - https://github.com/netty/netty/issues/2327
- // - https://github.com/netty/netty/issues/1764
- // 进行扩展全局字节缓冲区(容量大小 = 新数据追加到旧数据末尾组成新的全局字节缓冲区)
- buffer = expandCumulation(alloc, cumulation, in.readableBytes());
- } else {
- buffer = cumulation;
- }
- // 将新数据写入缓冲区
- buffer.writeBytes(in);
- // 释放当前的字节缓冲区的引用
- in.release();
- return buffer;
- }
- };
- /**
- * alloc 字节缓冲区操作类
- * cumulation 全局累加字节缓冲区
- * readable 读取到的字节数长度
- */
- // 字节缓冲区扩容方法
- static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf cumulation, int readable) {
- // 旧数据
- ByteBuf oldCumulation = cumulation;
- // 通过 ByteBufAllocator 将缓冲区扩大到 oldCumulation + readable 大小
- cumulation = alloc.buffer(oldCumulation.readableBytes() + readable);
- // 将旧数据重新写入到新的字节缓冲区
- cumulation.writeBytes(oldCumulation);
- // 旧字节缓冲区引用 - 1
- oldCumulation.release();
- return cumulation;
- }
3. ByteBuf 释放当前字节缓冲区的引用: 通过调用 ReferenceCounted 接口中的 release 方法来释放
- @Override
- public boolean release() {
- return release0(1);
- }
- @Override
- public boolean release(int decrement) {
- return release0(checkPositive(decrement, "decrement"));
- }
- /**
- * decrement 减量
- */
- private boolean release0(int decrement) {
- for (;;) {
- int refCnt = this.refCnt;
- // 当前引用小于减量
- if (refCnt <decrement) {
- throw new IllegalReferenceCountException(refCnt, -decrement);
- }
- // 这里就利用里线程并发中的知识 CAS, 线程安全的设置 refCnt 的值
- if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
- // 如果减量和引用量相等
- if (refCnt == decrement) {
- // 全部释放
- deallocate();
- return true;
- }
- return false;
- }
- }
- }
4. 将全局字节缓冲区进行解码
- /**
- * ctx ChannelHandler 的上下文, 用于传输数据与下一个 handler 来交互
- * in 入站数据
- * out 解析之后的出站集合 (此出站不是返回给客户端的而是传递给下个 handler 的)
- */
- protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
- try {
- // 如果入站数据还有没解析的
- while (in.isReadable()) {
- // 解析成功的出站集合长度
- int outSize = out.size();
- // 如果大于 0 则说明解析成功的数据还没被消费完, 直接 fire 掉给通道中的后续 handler 继续 消费
- if (outSize> 0) {
- fireChannelRead(ctx, out, outSize);
- out.clear();
- // Check if this handler was removed before continuing with decoding.
- // 在这个 handler 删除之前检查是否还在继续解码
- // If it was removed, it is not safe to continue to operate on the buffer.
- // 如果移除了, 它继续操作缓冲区是不安全的
- //
- // See:
- // - https://github.com/netty/netty/issues/4635
- if (ctx.isRemoved()) {
- break;
- }
- outSize = 0;
- }
- // 入站数据字节长度
- int oldInputLength = in.readableBytes();
- // 开始解码数据
- decodeRemovalReentryProtection(ctx, in, out);
- // Check if this handler was removed before continuing the loop.
- //
- // If it was removed, it is not safe to continue to operate on the buffer.
- //
- // See https://github.com/netty/netty/issues/1664
- if (ctx.isRemoved()) {
- break;
- }
- // 解析完毕跳出循环
- if (outSize == out.size()) {
- if (oldInputLength == in.readableBytes()) {
- break;
- } else {
- continue;
- }
- }
- if (oldInputLength == in.readableBytes()) {
- throw new DecoderException(
- StringUtil.simpleClassName(getClass()) +
- ".decode() did not read anything but decoded a message.");
- }
- if (isSingleDecode()) {
- break;
- }
- }
- } catch (DecoderException e) {
- throw e;
- } catch (Throwable cause) {
- throw new DecoderException(cause);
- }
- }
- final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
- // 设置解码状态为正在解码 STATE_INIT = 0; STATE_CALLING_CHILD_DECODE = 1; STATE_HANDLER_REMOVED_PENDING = 2; 分别为初始化; 解码; 解码完毕移除
- decodeState = STATE_CALLING_CHILD_DECODE;
- try {
- // 具体的解码逻辑(netty 提供的解码器或自定义解码器中重写的 decode 方法)
- decode(ctx, in, out);
- } finally {
- // 此时 decodeState 为正在解码中 值为 1, 返回 false
- boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
- // 在设置为初始化等待解码
- decodeState = STATE_INIT;
- // 解码完成移除当前 ChannelHandler 标记为不处理
- // 可以看看 handlerRemoved 源码. 如果缓冲区还有数据直接传递给下一个 handler
- if (removePending) {
- handlerRemoved(ctx);
- }
- }
- }
5. 执行 channelReadComplete
- @Override
- public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
- // 读取次数重置
- numReads = 0;
- // 重置读写 index
- discardSomeReadBytes();
- // 在 channelRead meth 中定义赋值 decodeWasNull = !out.insertSinceRecycled();
- // out 指的是解码集合 List<Object> out; 咱们可以点进
- if (decodeWasNull) {
- decodeWasNull = false;
- if (!ctx.channel().config().isAutoRead()) {
- ctx.read();
- }
- }
- // fire 掉 readComplete 传递到下一个 handler 的 readComplete
- ctx.fireChannelReadComplete();
- }
- /**
- * 然后我们可以搜索下 insertSinceRecucled 在什么地方被赋值了
- * Returns {@code true} if any elements where added or set. This will be reset once {@link #recycle()} was called.
- */
- boolean insertSinceRecycled() {
- return insertSinceRecycled;
- }
- // 搜索下 insert 的调用我们可以看到是 CodecOutputList 类即为 channelRead 中的 out 集合, 众所周知在 decode 完之后, 解码数据就会被调用 add 方法, 此时 insertSinceRecycled 被设置为 true
- private void insert(int index, Object element) {
- array[index] = element;
- insertSinceRecycled = true;
- }
- /**
- * 清空回收数组内部的所有元素和存储空间
- * Recycle the array which will clear it and null out all entries in the internal storage.
- */
- // 搜索 recycle 的调用我么可以知道在 channelRead 的 finally 逻辑中 调用了 out.recycle(); 此时 insertSinceRecycled 被设置为 false
- void recycle() {
- for (int i = 0 ; i <size; i ++) {
- array[i] = null;
- }
- clear();
- insertSinceRecycled = false;
- handle.recycle(this);
- }
至此 ByteToMessageDecoder 解码类应该差不多比较清晰了!!!
MessageToByteEncoder: 编码超类, 将消息转成字节进行编码发出
何谓编码, 就是将发送数据转化为客户端和服务端约束好的数据结构和格式进行传输, 我们可以在编码过程中将消息体 body 的长度和一些头部信息有序的设置到 ByteBuf 字节缓冲区中; 方便解码方灵活的运用来判断 (是否完整的包等) 和处理业务; 解码是继承入站数据, 反之编码应该继承出站的数据; 接下来我们看看编码类是怎么进行编码的;
MessageToByteEncoder 类图如下
源码分析
既然是继承出站类, 我们直接看看 write 方法是怎么样的
- /**
- * 通过 write 方法获取到出站的数据即要发送出去的数据
- * ctx channelHandler 上下文
- * msg 发送的数据 Object 可以通过继承类指定的泛型来指定
- * promise channelPromise 异步监听, 类似 ChannelFuture, 只不过 promise 可以设置监听的结果, future 只能通过获取监听的成功失败结果; 可以去了解下 promise 和 future 的区别
- */
- @Override
- public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
- ByteBuf buf = null;
- try {
- // 检测发送数据的类型 通过 TypeParameterMatcher 类型匹配器
- if (acceptOutboundMessage(msg)) {
- @SuppressWarnings("unchecked")
- I cast = (I) msg;
- // 分配字节缓冲区 preferDirect 默认为 true
- buf = allocateBuffer(ctx, cast, preferDirect);
- try {
- // 进行编码
- encode(ctx, cast, buf);
- } finally {
- // 完成编码后释放对象的引用
- ReferenceCountUtil.release(cast);
- }
- // 如果缓冲区有数据则通过 ctx 发送出去, promise 可以监听数据传输并设置是否完成
- if (buf.isReadable()) {
- ctx.write(buf, promise);
- } else {
- // 如果没有数据则释放字节缓冲区的引用并发送一个 empty 的空包
- buf.release();
- ctx.write(Unpooled.EMPTY_BUFFER, promise);
- }
- buf = null;
- } else {
- // 非 TypeParameterMatcher 类型匹配器匹配的类型直接发送出去
- ctx.write(msg, promise);
- }
- } catch (EncoderException e) {
- throw e;
- } catch (Throwable e) {
- throw new EncoderException(e);
- } finally {
- if (buf != null) {
- buf.release();
- }
- }
- }
- // 初始化设置 preferDirect 为 true
- protected MessageToByteEncoder() {
- this(true);
- }
- protected MessageToByteEncoder(boolean preferDirect) {
- matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
- this.preferDirect = preferDirect;
- }
编码: 重写 encode 方法, 根据实际业务来进行数据编码
- // 此处就是我们需要重写的编码方法了, 我们和根据约束好的或者自己定义好想要的数据格式发送给对方
- // 下面是我自己写的 demo 的编码方法; 头部设置好 body 的长度, 服务端可以根据长度来判断是否是完整的包, 仅仅自学写的简单的 demo 非正常线上运营项目的逻辑
- public class MyClientEncode extends MessageToByteEncoder<String> {
- @Override
- protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
- if (null != msg) {
- byte[] request = msg.getBytes(Charset.forName("UTF-8"));
- out.writeInt(request.length);
- out.writeBytes(request);
- }
- }
- }
编码类相对要简单很多, 因为只需要将发送的数据序列化, 按照一定的格式进行发送数据!!!
项目实战
项目主要简单的实现下自定义编解码器的运用及 LengthFieldBasedFrameDecoder 的使用
项目结构如下
│ hetangyuese-netty-06.iml
│ pom.xml
│
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─hetangyuese
│ │ │ └─netty
│ │ │ ├─client
│ │ │ │ MyClient06.java
│ │ │ │ MyClientChannelInitializer.java
│ │ │ │ MyClientDecoder.java
│ │ │ │ MyClientEncode.java
│ │ │ │ MyClientHandler.java
│ │ │ │ MyMessage.java
│ │ │ │
│ │ │ └─server
│ │ │ MyChannelInitializer.java
│ │ │ MyServer06.java
│ │ │ MyServerDecoder.java
│ │ │ MyServerDecoderLength.java
│ │ │ MyServerEncoder.java
│ │ │ MyServerHandler.java
│ │ │
│ │ └─resources
│ └─test
│ └─java
服务端
Serverhandler: 只是简单的将解码的内容输出
- public class MyServerHandler extends ChannelInboundHandlerAdapter {
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- System.out.println("客户端连接成功 time:" + new Date().toLocaleString());
- }
- @Override
- public void channelInactive(ChannelHandlerContext ctx) throws Exception {
- System.out.println("客户端断开连接 time:" + new Date().toLocaleString());
- }
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- String body = (String) msg;
- System.out.println("content:" + body);
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- // 出现异常关闭通道
- cause.printStackTrace();
- ctx.close();
- }
- }
解码器
- public class MyServerDecoder extends ByteToMessageDecoder {
- // 此处我头部只塞了长度字段占 4 个字节, 别问为啥我知道, 这是要客户端和服务端约束好的
- private static int min_head_length = 4;
- @Override
- protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
- // 解码的字节长度
- int size = in.readableBytes();
- if(size <min_head_length) {
- System.out.println("解析的数据长度小于头部长度字段的长度");
- return ;
- }
- // 读取的时候指针已经移位到长度字段的尾端
- int length = in.readInt();
- if (size < length) {
- System.out.println("解析的数据长度与长度不符合");
- return ;
- }
- // 上面已经读取到了长度字段, 后面的长度就是 body
- ByteBuf decoderArr = in.readBytes(length);
- byte[] request = new byte[decoderArr.readableBytes()];
- // 将数据写入空数组
- decoderArr.readBytes(request);
- String body = new String(request, Charset.forName("UTF-8"));
- out.add(body);
- }
- }
将解码器加入到 channelHandler 中: 记得加到业务 handler 的前面否则无效
- public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- ch.pipeline()
- // .addLast(new MyServerDecoderLength(10240, 0, 4, 0, 0))
- // .addLast(new LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0))
- .addLast(new MyServerDecoder())
- .addLast(new MyServerHandler())
- ;
- }
- }
客户端
- ClientHandler
- public class MyClientHandler extends ChannelInboundHandlerAdapter {
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- System.out.println("与服务端连接成功");
- for (int i = 0; i<10; i++) {
- ctx.writeAndFlush("hhhhh" + i);
- }
- }
- @Override
- public void channelInactive(ChannelHandlerContext ctx) throws Exception {
- System.out.println("与服务端断开连接");
- }
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- System.out.println("收到服务端消息:" +msg+ "time:" + new Date().toLocaleString());
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- cause.printStackTrace();
- ctx.close();
- }
- }
编码器
- public class MyClientEncode extends MessageToByteEncoder<String> {
- @Override
- protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
- if (null != msg) {
- byte[] request = msg.getBytes(Charset.forName("UTF-8"));
- out.writeInt(request.length);
- out.writeBytes(request);
- }
- }
- }
将编码器加到 ClientHandler 的前面
- public class MyClientChannelInitializer extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- ch.pipeline()
- .addLast(new MyClientDecoder())
- .addLast(new MyClientEncode())
- .addLast(new MyClientHandler())
- ;
- }
- }
服务端运行结果
MyServer06 is start ...................
客户端连接成功 time: 2019-11-19 16:35:47
- content:hhhhh0
- content:hhhhh1
- content:hhhhh2
- content:hhhhh3
- content:hhhhh4
- content:hhhhh5
- content:hhhhh6
- content:hhhhh7
- content:hhhhh8
- content:hhhhh9
如果不用自定义的解码器怎么获取到 body 内容呢
将自定义编码器换成 LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0)
- public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- ch.pipeline()
- // .addLast(new MyServerDecoderLength(10240, 0, 4, 0, 0))
- .addLast(new LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0))
- // .addLast(new MyServerDecoder())
- .addLast(new MyServerHandler())
- ;
- }
- }
- // 怕忘记的各个参数的含义在这在说明一次, 自己不断的修改每个值观察结果就可以更加深刻的理解
- /**
- * maxFrameLength: 消息体的最大长度, 好像默认最大值为 1024*1024
- * lengthFieldOffset 长度字段所在字节数组的下标 (我这是第一个 write 的所以下标是 0)
- * lengthFieldLength 长度字段的字节长度(int 类型占 4 个字节)
- * lengthAdjustment 长度字段补偿的数值 (lengthAdjustment = 数据包长度 - lengthFieldOffset - lengthFieldLength - 长度域的值), 解析需要减去对应的数值
- * initialBytesToStrip 是否去掉长度字段(0 不去除, 对应长度域字节长度)
- */
- public LengthFieldBasedFrameDecoder(
- int maxFrameLength,
- int lengthFieldOffset, int lengthFieldLength,
- int lengthAdjustment, int initialBytesToStrip)
结果: 前都带上了长度
MyServer06 is start ...................
客户端连接成功 time: 2019-11-19 17:53:42
收到客户端发来的消息: hhhhh0, time: 2019-11-19 17:53:42
收到客户端发来的消息: hhhhh1, time: 2019-11-19 17:53:42
收到客户端发来的消息: hhhhh2, time: 2019-11-19 17:53:42
收到客户端发来的消息: hhhhh3, time: 2019-11-19 17:53:42
收到客户端发来的消息: hhhhh4, time: 2019-11-19 17:53:42
收到客户端发来的消息: hhhhh5, time: 2019-11-19 17:53:42
收到客户端发来的消息: hhhhh6, time: 2019-11-19 17:53:42
收到客户端发来的消息: hhhhh7, time: 2019-11-19 17:53:42
收到客户端发来的消息: hhhhh8, time: 2019-11-19 17:53:42
收到客户端发来的消息: hhhhh9, time: 2019-11-19 17:53:42
如果我们在客户端的长度域中做手脚 LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0)
旧: out.writeInt(request.length);
新: out.writeInt(request.length + 1);
- // 看结果就不正常, 0 后面多了一个 0; 但是不知道为啥只解码了一次??? 求解答
- MyServer06 is start ...................
客户端连接成功 time: 2019-11-19 17:56:55
收到客户端发来的消息: hhhhh0 , time: 2019-11-19 17:56:55
- // 正确修改为 LengthFieldBasedFrameDecoder(10240, 0, 4, -1, 0)
- // 结果:
- MyServer06 is start ...................
客户端连接成功 time: 2019-11-19 18:02:18
收到客户端发来的消息: hhhhh0, time: 2019-11-19 18:02:18
收到客户端发来的消息: hhhhh1, time: 2019-11-19 18:02:18
收到客户端发来的消息: hhhhh2, time: 2019-11-19 18:02:18
收到客户端发来的消息: hhhhh3, time: 2019-11-19 18:02:18
收到客户端发来的消息: hhhhh4, time: 2019-11-19 18:02:18
收到客户端发来的消息: hhhhh5, time: 2019-11-19 18:02:18
收到客户端发来的消息: hhhhh6, time: 2019-11-19 18:02:18
收到客户端发来的消息: hhhhh7, time: 2019-11-19 18:02:18
收到客户端发来的消息: hhhhh8, time: 2019-11-19 18:02:18
收到客户端发来的消息: hhhhh9, time: 2019-11-19 18:02:18
舍弃长度域 :LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 4)
- // 结果
- MyServer06 is start ...................
客户端连接成功 time: 2019-11-19 18:03:44
收到客户端发来的消息: hhhhh0, time: 2019-11-19 18:03:44
收到客户端发来的消息: hhhhh1, time: 2019-11-19 18:03:44
收到客户端发来的消息: hhhhh2, time: 2019-11-19 18:03:44
收到客户端发来的消息: hhhhh3, time: 2019-11-19 18:03:44
收到客户端发来的消息: hhhhh4, time: 2019-11-19 18:03:44
收到客户端发来的消息: hhhhh5, time: 2019-11-19 18:03:44
收到客户端发来的消息: hhhhh6, time: 2019-11-19 18:03:44
收到客户端发来的消息: hhhhh7, time: 2019-11-19 18:03:44
收到客户端发来的消息: hhhhh8, time: 2019-11-19 18:03:44
收到客户端发来的消息: hhhhh9, time: 2019-11-19 18:03:44
分析源码示例中的 lengthAdjustment = 消息字节长度 - lengthFieldOffset-lengthFieldLength - 长度域中的值
源码中的示例
- * <pre>
- * lengthFieldOffset = 0
- * lengthFieldLength = 2
- * <b>lengthAdjustment</b> = <b>-2</b> (= the length of the Length field)
- * initialBytesToStrip = 0
- *
- * BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
- * +--------+----------------+ +--------+----------------+
- * | Length | Actual Content |----->| Length | Actual Content |
- * | 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" |
- * +--------+----------------+ +--------+----------------+
- * </pre>
长度域中 0x000E 为 16 进制, 转换成 10 进制是 14, 说明消息体长度为 14; 根据公式: 14-0-2-14 = -2
- * <pre>
- * lengthFieldOffset = 0
- * lengthFieldLength = 3
- * <b>lengthAdjustment</b> = <b>2</b> (= the length of Header 1)
- * initialBytesToStrip = 0
- *
- * BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
- * +----------+----------+----------------+ +----------+----------+----------------+
- * | Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content |
- * | 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" |
- * +----------+----------+----------------+ +----------+----------+----------------+
- * </pre>
从上的例子可以知道; lengthAdjustment(2) = 17- 12(00000C)-lengthFieldOffset(0) - lengthFieldLength(3);
....... 等等
来源: https://www.cnblogs.com/hetangyuese/p/11891374.html