Netty(三) 什么是 TCP 拆、粘包?如何解决? (2)

LineBasedFrameDecoder 解码器使用非常简单,只需要在 pipline 链条上添加即可。

//字符串解析,换行防拆包 .addLast(new LineBasedFrameDecoder(1024)) .addLast(new StringDecoder())

构造函数中传入了 1024 是指报的长度最大不超过这个值,具体可以看下文的源码分析。

然后我们再进行一次测试看看结果:

注意,由于 LineBasedFrameDecoder 解码器是通过换行符来判断的,所以在发送时,一条完整的消息需要加上 \n。

Netty(三) 什么是 TCP 拆、粘包?如何解决?

最终的结果:

Netty(三) 什么是 TCP 拆、粘包?如何解决?

仔细观察日志,发现确实没有一条被拆、粘包。

LineBasedFrameDecoder 的原理

目的达到了,来看看它的实现原理:

Netty(三) 什么是 TCP 拆、粘包?如何解决?

第一步主要就是 findEndOfLine 方法去找到当前报文中是否存在分隔符,存在就会返回分隔符所在的位置。

判断是否需要丢弃,默认为 false ,第一次走这个逻辑(下文会判断是否需要改为 true)。

如果报文中存在换行符,就会将数据截取到那个位置。

如果不存在换行符(有可能是拆包、粘包),就看当前报文的长度是否大于预设的长度。大于则需要缓存这个报文长度,并将 discarding 设为 true。

如果是需要丢弃时,判断是否找到了换行符,存在则需要丢弃掉之前记录的长度然后截取数据。

如果没有找到换行符,则将之前缓存的报文长度进行累加,用于下次抛弃。

从这个逻辑中可以看出就是寻找报文中是否包含换行符,并进行相应的截取。

由于是通过缓冲区读取的,所以即使这次没有换行符的数据,只要下一次的报文存在换行符,上一轮的数据也不会丢。

高效的编码方式 Google Protocol

上面提到的其实就是在解码中进行操作,我们也可以自定义自己的拆、粘包工具。

编解码的主要目的就是为了可以编码成字节流用于在网络中传输、持久化存储。

Java 中也可以实现 Serializable 接口来实现序列化,但由于它性能等原因在一些 RPC 调用中用的很少。

而 Google Protocol 则是一个高效的序列化框架,下面来演示在 Netty 中如何使用。

安装

首先第一步自然是安装:

在官网下载对应的包。

本地配置环境变量:

Netty(三) 什么是 TCP 拆、粘包?如何解决?

当执行 protoc --version 出现以下结果表明安装成功:

Netty(三) 什么是 TCP 拆、粘包?如何解决?

定义自己的协议格式

接着是需要按照官方要求的语法定义自己的协议格式。

比如我这里需要定义一个输入输出的报文格式:

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=http://www.likecs.com/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 所以就设置了对应的实例。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfsww.html