安装
首先第一步自然是安装:
在官网下载对应的包。
本地配置环境变量:
当执行 protoc --version
出现以下结果表明安装成功:
定义自己的协议格式
接着是需要按照官方要求的语法定义自己的协议格式。
比如我这里需要定义一个输入输出的报文格式:
BaseRequestProto.proto:
syntax = "proto2"; package protocol; option java_package = "com.crossoverjie.netty.action.protocol"; option java_outer_classname = "BaseRequestProto"; message RequestProtocol { required int32 requestId = 2; required string reqMsg = 1; }
BaseResponseProto.proto:
syntax = "proto2"; package protocol; option java_package = "com.crossoverjie.netty.action.protocol"; option java_outer_classname = "BaseResponseProto"; message ResponseProtocol { required int32 responseId = 2; required string resMsg = 1; }
再通过
protoc --java_out=/dev BaseRequestProto.proto BaseResponseProto.proto
protoc 命令将刚才定义的协议格式转换为 Java 代码,并生成在 /dev
目录。
只需要将生成的代码拷贝到我们的项目中,同时引入依赖:
<dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.4.0</version> </dependency>
利用 Protocol 的编解码也非常简单:
public class ProtocolUtil { public static void main(String[] args) throws InvalidProtocolBufferException { BaseRequestProto.RequestProtocol protocol = BaseRequestProto.RequestProtocol.newBuilder() .setRequestId(123) .setReqMsg("你好啊") .build(); byte[] encode = encode(protocol); BaseRequestProto.RequestProtocol parseFrom = decode(encode); System.out.println(protocol.toString()); System.out.println(protocol.toString().equals(parseFrom.toString())); } /** * 编码 * @param protocol * @return */ public static byte[] encode(BaseRequestProto.RequestProtocol protocol){ return protocol.toByteArray() ; } /** * 解码 * @param bytes * @return * @throws InvalidProtocolBufferException */ public static BaseRequestProto.RequestProtocol decode(byte[] bytes) throws InvalidProtocolBufferException { return BaseRequestProto.RequestProtocol.parseFrom(bytes); } }
利用 BaseRequestProto
来做一个演示,先编码再解码最后比较最终的结果是否相同。答案肯定是一致的。
利用 protoc 命令生成的 Java 文件里已经帮我们把编解码全部都封装好了,只需要简单调用就行了。
可以看出 Protocol 创建对象使用的是构建者模式,对使用者来说清晰易读,更多关于构建器的内容可以参考这里。
更多关于 Google Protocol
内容请查看官方开发文档。
结合 Netty
Netty 已经自带了对 Google protobuf 的编解码器,也是只需要在 pipline 中添加即可。
server 端:
// google Protobuf 编解码 .addLast(new ProtobufDecoder(BaseRequestProto.RequestProtocol.getDefaultInstance())) .addLast(new ProtobufEncoder())
客户端:
// google Protobuf 编解码 .addLast(new ProtobufDecoder(BaseResponseProto.ResponseProtocol.getDefaultInstance())) .addLast(new ProtobufEncoder())
稍微注意的是,在构建 ProtobufDecoder 时需要显式指定解码器需要解码成什么类型。
我这里服务端接收的是 BaseRequestProto,客户端收到的是服务端响应的 BaseResponseProto 所以就设置了对应的实例。
同样的提供了一个接口向服务端发送消息,当服务端收到了一个特殊指令时也会向客户端返回内容:
@Override protected void channelRead0(ChannelHandlerContext ctx, BaseRequestProto.RequestProtocol msg) throws Exception { LOGGER.info("收到msg={}", msg.getReqMsg()); if (999 == msg.getRequestId()){ BaseResponseProto.ResponseProtocol responseProtocol = BaseResponseProto.ResponseProtocol.newBuilder() .setResponseId(1000) .setResMsg("服务端响应") .build(); ctx.writeAndFlush(responseProtocol) ; } }
在 swagger 中调用相关接口:
在日志可以看到服务端收到了消息,同时客户端也收到了返回:
虽说 Netty 封装了 Google Protobuf 相关的编解码工具,其实查看它的编码工具就会发现也是利用上文提到的 api 实现的。
Protocol 拆、粘包
Google Protocol 的使用确实非常简单,但还是有值的注意的地方,比如它依然会有拆、粘包问题。
不妨模拟一下:
连续发送 100 次消息看服务端收到的怎么样:
会发现服务端在解码的时候报错,其实就是被拆、粘包了。
这点 Netty 自然也考虑到了,所以已经提供了相关的工具。
//拆包解码 .addLast(new ProtobufVarint32FrameDecoder()) .addLast(new ProtobufVarint32LengthFieldPrepender())
只需要在服务端和客户端加上这两个编解码工具即可,再来发送一百次试试。
查看日志发现没有出现一次异常,100 条信息全部都接收到了。
这个编解码工具可以简单理解为是在消息体中加了一个 32 位长度的整形字段,用于表明当前消息长度。
总结
网络这块同样是计算机的基础,由于近期在做相关的工作所以接触的比较多,也算是给大学补课了。
后面会接着更新 Netty 相关的内容,最后会产出一个高性能的 HTTP 以及 RPC 框架,敬请期待。
上文相关的代码: