package com.shiyanlou.server.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CalcDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//读取请求头,定义的是4个字节,不够的话直接返回等待下一次报文
if (in.readableBytes() < 4) {
return;
}
//readInt()方法就是读取4个byte并转成int
int contentLength = in.readInt();
//如果剩余可读的字节数比请求体长度小,还原ByteBuf下标等待下一次请求报文
if (in.readableBytes() < contentLength) {
in.readerIndex(in.readerIndex() - 4);
return;
}
//读取请求体
byte[] content = new byte[contentLength];
in.readBytes(content);
//使用正则进行匹配,解析请求体并转换成对象
Pattern pattern = Pattern.compile("^(\\d+)([+\\-*/])(\\d+)$");
Matcher matcher = pattern.matcher(new String(content));
if (matcher.find()) {
int num1 = Integer.valueOf(matcher.group(1));
String symbol = matcher.group(2);
int num2 = Integer.valueOf(matcher.group(3));
CalcBean calcBean = new CalcBean();
calcBean.setNum1(num1);
calcBean.setNum2(num2);
calcBean.setSymbol(symbol);
System.out.println("接收到请求体:" + calcBean.toString());
out.add(calcBean);
}
}
}
如果ByteBuf body 后边有新的数据本次会未读完,本次不会处理:
这里要注意一点是在decode()方法中,如果ByteBuf未读完,在下一次请求报文过来时,netty 会自动处理ByteBuf,读下标(readerIndex)还是接着上一次请求的下标,而不是默认将ByteBuf读完。
序列化
JDK 序列化的优缺点
通过上面的代码可以看到,只需要按 JDK 序列化的标准来声明类,就可以使用 netty 内置的编解码器来进行网络开发。那么肯定有人会问了,既然 JDK 的序列化用起来这么方便,为什么还要用别的序列化方案呢,这里将 JDK 序列化缺点说一下:
不支持跨语言
序列化/反序列化速度太慢
序列化后的体积太大
版本兼容问题
可以看到 JDK 序列化的缺点还是很多的,特别是不支持跨语言这点非常关键,这意味着如果使用 JDK 序列化的话那么客户端和服务器都得使用 java 来编写,而在大型项目中往往不止使用 java 一种语言来进行开发,所以接下来就要讲讲主流的序列化框架并与 netty 集成,最后再对比三种序列化框架的区别,好让团队在技术选型时能做出更好的选择。
Protocol Buffers
Protocol Buffers(之后简称为 protobuf)是由 Google 推出的一个与平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议,它需要提供一份 proto 文件来描述对应的数据结构,然后通过 protobuf 提供的编译器(protoc),可以将 proto 文件编译成各个语言的代码(Java、JavaScript、C、C++等等)然后通过生成的代码就可以直接使用其中的序列化和反序列化方法。
注意:本实验都是基于protobuf3.x版本进行开发。
./protoc-3.6.1/bin/protoc --proto_path=src/main/proto --java_out=src/main/java CalcRequest.proto
./protoc-3.6.1/bin/protoc --proto_path=src/main/proto --java_out=src/main/java CalcResponse.proto
protobuf 支持多语言,编码后的消息非常小,利于传输和存储,编码性能也是非常的高,并且支持不同版本的向下兼容,可以看到 JDK 序列化的缺点在 protobuf 这里基本不存在了,而 protobuf 唯一存在的缺点就是需要编写 proto 文件,这无疑会增加了代码的工作量,那肯定就有同学会问了,有没有不需要编写 proto 文件就能实现以上功能的序列化框架呢,当然是有的,接下来讲一讲 hessian 序列化。
hessian 序列化
hessian是一个 RPC 框架,它提供了基于二进制协议的 RPC 功能,但是这里只需要用到它提供的序列化功能,由于在 netty 中默认是没有提供基于 hessian 序列化的编解码器,所以这一节将会手动编写一个基于 hessian 序列化的编解码器。
hessian 序列化的优点在上一节其实已经讲到了,优点与 protobuf 类似,而且不用编写 proto 文件。缺点是序列化速度和序列化后消息的体积不如 protobuf,而且 hessian 已经很久没有进行更新了,在官网可以看到 hessian 最后的一个版本停止在了 2017 年 3 月 29 日发布的 4.0.51 版本。