Netty主流的编解码方式
Netty 编解码
Java原生的编解码 ①
简介
java 序列化只需要实现 java.io.Serializable 接口即可 但是有如下缺点
1,无法跨语言
2,序列化后码流太大
发送类 和 响应类
SubscribeResp
/**
* Netty 中 对 POJO 对象 进行Java 序列化
*
* 订购响应 应答 POJO类 定义
*/
public class SubscribeResp implements Serializable {
//默认序列号Id
private static final long serialVersionUID=3L;
private int subReqID;
private int respCode;
private String desc;
@Override
public String toString() {
return "SubscribeResp[" +
"subReqID=" + subReqID +
", respCode=" + respCode +
", desc='" + desc + '\'' +
']';
}
get set ...
}
SubscribeReq
/**
* Netty 中 对 POJO 对象 进行Java 序列化
*
* 订购请求POJO类 定义
*/
public class SubscribeReq implements Serializable {
//默认序列号Id
private static final long serialVersionUID=3L;
private int subReqID;
private String userName;
private String productName;
private String phoneNumber;
private String address;
@Override
public String toString() {
return "SubscribeReq[" +
"subReqID=" + subReqID +
", userName='" + userName + '\'' +
", productName='" + productName + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
", address='" + address + '\'' +
']';
}
get set ...
}
Protobuf的编解码 ②
简介
Protobuf全称Google Protocol Buffers,它由谷歌开源而来,在谷歌内部久经考验。它将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的POJO对象和Protobuf相关的方法和属性。它的特点如下。
1,结构化数据存储格式(XML, JSON等);
2,高效的编解码性能:
3,语言无关、平台无关、扩展性好;。
官方支持Java,C++和Python三种语言。首先我们来看下为什么不使用XML,尽管XML的可读性和可扩展性非常好,也非常适合描述数据结构,但是XML解析的时间开销和XML为了可读性而牺牲的空间开销都非常大,因此不适合做高性能的通信协议。Protobuf使用二进制编码,在空间和性能上具有更大的优势。
使用
这里需要把实体类转换成protobuf类
转换方式 看上一篇文章Protobuf的生成
JBoss Marshalling的编解码 ③
简介
JBoss Marshalling是一个Java对象的序列化API包,修正了JDK自带的序列化包的很多问题,但又保持跟java.io.Serializable接口的兼容;同时增加了一些可调的参数和附加的特性,并且这些参数和特性可通过工厂类进行配置。相比于传统的Java序列化机制,它的优点如下:
1 可插拔的类解析器,提供更加便捷的类加载定制策略,通过一个接口即可实现定制:
2 可插拔的对象替换技术,不需要通过继承的方式;
3 可插拔的预定义类缓存表,可以减小序列化的字节数组长度,提升常用类型的对象序列化性能; 无须实现java.io.Serializable接口,即可实现Java序列化;通过缓存技术提升对象的序列化性能。
相比于前面介绍的两种编解码框架, JBoss Marshalling更多是在JBoss内部使用,应用范围有限。
使用
直接导入jar 即可 ,和原生的一样
Marshalling编解码实体类和原生JAVA的是一样的
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
public class MarshallingCodeCFactory {
/**
* 创建 Jboss Marshalling解码器
*/
public static MarshallingDecoder buildMarshallingDecoder() {
//serial 表示创建的是java 序列化工厂对象
final MarshallerFactory marshallerFactory = Marshalling.getMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
//版本号
configuration.setVersion(5);
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024);
return decoder;
}
/**
* 创建 Jboss Marshalling 编码器
*/
public static MarshallingEncoder buildMarshallingEncoder() {
final MarshallerFactory marshallerFactory = Marshalling.getMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
//用于将序列化接口pojo对象转成二进制
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
这里 上述三种实现方式 都汇集在一起了。 具体看 ① ② ③ 注释`
服务端
SubReqServer
/**
* 服务端主函数
*/
public class SubReqServer {
public void bind(int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//①Netty 自带的 解码 编码 负责实现对序列化的POJO对象进行解码,
// socketChannel.pipeline().addLast(new ObjectDecoder(1024 * 1024, ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
// //在消息发送的时候自动将实现的pojo对象进行编码
// socketChannel.pipeline().addLast(new ObjectEncoder());
//②Protobuf 解码 编码-------
//ProtobufVarint32FrameDecoder:半包处理
// socketChannel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
// //告诉ProtobufDecoder 需要解码的目标类 似否则仅仅从字节数组中是无法判断要解码的目标类型//todo : 如果有多个目标。。咋办?
// socketChannel.pipeline().addLast(new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance()));
// socketChannel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
// socketChannel.pipeline().addLast(new ProtobufEncoder());
//③ 使用Marshalling
socketChannel.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
socketChannel.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
socketChannel.pipeline().addLast(new SubReqServerHandler());
}
});
//绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅退出
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
SubReqServerHandler
public class SubReqServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//已经被自动解码成了 SubscribeReq
//②Protobuf 解码 编码-------
// SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq) msg;
System.out.println("channelActive:=================111========");
// ① Netty ③ Marshalling
SubscribeReq req = (SubscribeReq) msg;
System.out.println("channelActive:========================="+req.getUserName());
//校验信息是否准确
if ("Joy Chen".equalsIgnoreCase(req.getUserName())) {
System.out.println("服务连接客户端序列化对象:[" + req.toString() + "]");
//消息 回馈给 客户端
if (req.getSubReqID() % 2 == 0) {
//① ③JDK自带的 解码 编码 负责实现对序列化的POJO对象进行解码,
ctx.writeAndFlush(resp(req.getSubReqID(), "购买成功 ,3天后发送到你的地址"));
//②Protobuf 解码 编码-------
// ctx.writeAndFlush(resp(req.getSubReqID()));
} else {
//① ③jdk自带的 解码 编码 负责实现对序列化的POJO对象进行解码,
ctx.writeAndFlush(resp(req.getSubReqID(), "购买失败 ,不给你"));
//②Protobuf 解码 编码-------
// ctx.writeAndFlush(resp(req.getSubReqID()));
}
}
}
//②Protobuf 解码 编码-------
private SubscribeRespProto.SubscribeResp resp(int subReqId) {
SubscribeRespProto.SubscribeResp.Builder builder = SubscribeRespProto.SubscribeResp.newBuilder();
builder.setSubReqID(subReqId);
builder.setRespCode(0);
builder.setDesc("购买成功三天后发给你");
return builder.build();
}
//① ③
public SubscribeResp resp(int subReqID, String msg) {
SubscribeResp resp = new SubscribeResp();
resp.setRespCode(0);
resp.setSubReqID(subReqID);
resp.setDesc(msg);
return resp;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();//发生异常关闭
}
}
客户端
SubReqClientHandler
/**
* 这里实体类 前后端的路径 务必保持一致
*/
class SubReqClientHandler : ChannelHandlerAdapter() {
override fun channelActive(ctx: ChannelHandlerContext?) {
println("channelActive:=========================")
for (i in 0..10) {
//① ③netty 自带 发送数据给 服务端
ctx!!.write(subReq(i))
//② Protobuf发送数据给服务端
// ctx!!.write(createSubscribeReq(i))
}
ctx!!.flush()
}
//① ③ 模拟请求数据
fun subReq(id: Int): SubscribeReq {
var req = SubscribeReq()
req.userName = "Joy Chen"
req.phoneNumber = "18806068888"
req.productName = "Netty 权威指南"
req.subReqID = id
req.address = "深圳市大梅沙海滨公园"
return req
}
override fun channelRead(ctx: ChannelHandlerContext?, msg: Any?) {
Log.e("CWJ-", "channelRead 注册服务响应:【${msg.toString()}】")
}
//② 模拟请求数据
private fun createSubscribeReq(i:Int): SubscribeReqProto.SubscribeReq? {
val builder = SubscribeReqProto.SubscribeReq.newBuilder()
builder.subReqID = i
builder.userName = "Joy Chen"
builder.productName = "netty book"
val address: MutableList<String> = ArrayList()
address.add("NanJing")
address.add("BeiJing")
address.add("ShenZhen")
builder.addAllAddress(address)
return builder.build()
}
override fun channelReadComplete(ctx: ChannelHandlerContext?) {
ctx!!.flush()
}
override fun exceptionCaught(ctx: ChannelHandlerContext?, cause: Throwable?) {
cause!!.printStackTrace()
ctx!!.close()
}
}
/**
* ProtobufDecoder仅仅负责解码,它不支持读半包。因此,在ProtobufDecoder前面,一定要有能够处理读半包的解码器,有三种方式可以选择。
* , 使用Netty提供的ProtobufVarint32FrameDecoder,它可以处理半包消息;
* 3 继承Netty提供的通用半包解码器LengthFieldBasedFrameDecoder;
* 继承ByteToMessageDecoder类, 自己处理半包消息。
*/
class SubReqClient {
fun connet(port: Int, host: String) {
//配置 客户端 NIO线程组
var group: EventLoopGroup = NioEventLoopGroup();
try {
var b = Bootstrap()
b.group(group).channel(NioSocketChannel::class.java)
.option(ChannelOption.TCP_NODELAY, true)
.handler(object : ChannelInitializer<SocketChannel>() {
override fun initChannel(ch: SocketChannel?) {
//① Netty自带的 解码 编码 负责实现对序列化的POJO对象进行解码,
// 禁止对类加载器进行缓存
// ch!!.pipeline().addLast(
// ObjectDecoder(
// 1024,
// ClassResolvers.cacheDisabled(this.javaClass.classLoader)
// )
// )
// ch!!.pipeline().addLast(ObjectEncoder())
//②Protobuf 方式 解码 编码-------
//ProtobufVarint32FrameDecoder:半包处理
// ch!!.pipeline().addLast(ProtobufVarint32FrameDecoder())
//告诉ProtobufDecoder 需要解码的目标类 似否则仅仅从字节数组中是无法判断要解码的目标类型//todo : 如果有多个目标。。咋办?
// ch!!.pipeline()
// .addLast(ProtobufDecoder(SubscribeRespProto.SubscribeResp.getDefaultInstance()))
// ch!!.pipeline().addLast(ProtobufVarint32LengthFieldPrepender())
// ch!!.pipeline().addLast(ProtobufEncoder())
//③Marshalling 方式
ch!!.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder())
ch!!.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder())
ch!!.pipeline().addLast(SubReqClientHandler())
}
})
//发起异步连接操作
var f = b.connect(host, port).sync()
//等待客户端链路关闭
f.channel().closeFuture().sync()
} finally {
//优雅退出,释放线程组
group.shutdownGracefully()
}
}
}
备注
ProtobufDecoder仅仅负责解码,它不支持读半包。因此,在ProtobufDecoder前面,一定要有能够处理读半包的解码器,有三种方式可以选择。
- , 使用Netty提供的ProtobufVarint32FrameDecoder,它可以处理半包消息;
- 3 继承Netty提供的通用半包解码器LengthFieldBasedFrameDecoder;
- 继承ByteToMessageDecoder类, 自己处理半包消息。
SubReqClient
结尾
参考《Netty权威指南》