概述
本文通过一个具体的案例来详细介绍 LengthFieldBasedFrameDecoder 的使用,在案例中通过对数据的封包和解包实现对数据的加密,压缩,解压,解密等操作。
关键点
- 服务器端在启动的时候开放一个端口:19080
- 客户端在启动的时候通过 ip 和 端口连上服务器端
- 客户端和服务器端都通过 Channel 对象向彼此发送数据
- 服务器和客户端都通过继承 ChannelInboundHandlerAdapter 类实现对 消息实体 的读取和回写等操作
- 服务器和客户端都通过 LengthFieldBasedFrameDecoder 和 CommonDataEncoder 实现对 消息实体 的解码和转码操作
- 服务器和客户端启动的时候都会阻塞当前线程,因此需要在一个单独的线程中进行启动
- 服务器和客户端都在 发送数据 前 进行加密和压缩
- 服务器和客户端都在 接收数据 后 进行解压和解密
- 服务器和客户端需要确保发送的数据的字节长度不能超过 LengthFieldBasedFrameDecoder 设置的最大帧长度
使用案例介绍
- 本例是一个 spring boot web 项目,项目占用了 8080 端口
- 服务器端在启动的时候开放 19080 端口(注意不要和 web 端口冲突了)
- 客户端在启动的时候连上服务器端
- 通过 web api 向客户端发送数据,客户端再通过 Channel 对象向服务器端发送数据
- 服务器接收到客户端数据后也通过 Channel 对象向客户端发送数据
- 客户端和服务器端通过 CommonData 对象进行消息的封装和传输,并通过 LengthFieldBasedFrameDecoder 实现对数据的解码。
消息实体 CommonData
- CommonData 的定义如下
1 | @Data |
LengthFieldBasedFrameDecoder 构造方法介绍
1 | public LengthFieldBasedFrameDecoder( |
那么这几个重要的参数如下:
- maxFrameLength:最大帧长度。也就是可以接收的数据的最大长度。如果超过,此次数据会被丢弃。
- lengthFieldOffset:长度域偏移。就是说数据开始的几个字节可能不是表示数据长度,需要后移几个字节才是长度域。
- lengthFieldLength:长度域字节数。用几个字节来表示数据长度。
- lengthAdjustment:数据长度修正。因为长度域指定的长度可以使 header+body 的整个长度,也可以只是 body 的长度。如果表示 header+body 的整个长度,那么我们需要修正数据长度。
- initialBytesToStrip:跳过的字节数。如果你需要接收 header+body 的所有数据,此值就是 0,如果你只想接收 body 数据,那么需要跳过 header 所占用的字节数。
LengthFieldBasedFrameDecoder 的构造参数
LengthFieldBasedFrameDecoder 是 Netty 中解决拆包粘包问题的一个重要的类,主要结构就是 header+body 结构。只需要传入正确的参数就可以发送和接收正确的数据。
- 根据 CommonData 的定义,可以知道对应 LengthFieldBasedFrameDecoder 的构造参数如下
1 | private static final int MAX_FRAME_LENGTH = 1024 * 1024; |
对消息实体 CommonData 进行加密,压缩,解压,解密等操作
- 将 对象转成 加密,压缩 后的字节数组
- 将 字节数组 解压,解密 后转成 java 对象
1 | /** |
通用消息转码类 CommonDataEncoder
- 服务器端和客户端在 业务处理类 都是直接将 消息体 直接写入的 channel 中的
- 在 CommonDataEncoder 类中才会对 消息体 进行 加密和压缩处理
- 服务器端和客户端分别在 业务处理类 中对 消息体 进行解压和解密处理
1 | package com.ckjava.test.netty.encoder; |
服务器端启动类
- 通过 init 方法启动 Netty Server
- 通过 childHandler 绑定 CommonDataEncoder,LengthFieldBasedFrameDecoder,CommonServerDataHandler
1 | package com.ckjava.test.server; |
服务器端业务处理类
- 继承 SimpleChannelInboundHandler 类,用于接收客户端发送的消息体,并向客户端返回数据。
- 通过
CommonData.fromBytes(byteBuf)
将接收的数据进行解压,解密 - 向客户端返回数据由 CommonDataEncoder 进行加密和压缩处理
- 通过 handleMap 封装具体的业务逻辑
1 | package com.ckjava.test.server; |
客户端启动类
- 通过 init 方法启动客户端
- 通过 childHandler 绑定 CommonDataEncoder,LengthFieldBasedFrameDecoder,CommonServerDataHandler
1 | package com.ckjava.test.client; |
客户端业务处理类
- 通过 channelRead0 方法读取服务器端发送过来的数据
- 通过 channelActive 方法在启动的时候主动向服务器端发送消息
- 在接收消息的时候需要进行解压,解密操作
1 | package com.ckjava.test.client; |
通过 api 向 Netty client 发送数据
这里为了测试方便,通过 api 向 Netty client 发送数据,Netty client 再将数据发送给 服务器端。
1 | package com.ckjava.test.web; |
测试
- 通过 api 发送数据如下
1 | curl -X POST "http://localhost:8080/nettyClient" -H "accept: application/json;charset=utf-8" -H "Content-Type: application/json" -d "{ \"body\": \"ckjava\", \"flag\": 1, \"length\": 0, \"type\": 1}" |
- 输出如下
1 | 16:48:09.471 [nioEventLoopGroup-3-1] INFO c.c.test.server.CommonNettyServer - 服务器启动成功,并监听端口:19080 |