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

简介: 记得前段时间我们生产上的一个网关出现了故障。 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信。 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议。 有个前提是:网关是需要读取一段完整的报文才能进行后面的逻辑。

安装


首先第一步自然是安装:


官网下载对应的包。


本地配置环境变量:



当执行 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 框架,敬请期待。


上文相关的代码:


github.com/crossoverJi…


相关文章
|
6月前
|
网络协议 Java Maven
基于Netty实现TCP通信
基于Netty实现TCP通信
101 0
|
1月前
|
网络协议 前端开发
netty的TCP服务端和客户端实现
本文介绍了使用Netty框架实现TCP服务端和客户端的步骤,包括添加Netty依赖、编写服务端和客户端的代码,涉及NioEventLoopGroup、ServerBootstrap、Bootstrap、ChannelInitializer等核心组件,以及如何启动服务端监听和客户端连接。
148 4
|
3月前
|
编解码 网络协议 开发者
Netty运行原理问题之NettyTCP的粘包和拆包的问题如何解决
Netty运行原理问题之NettyTCP的粘包和拆包的问题如何解决
|
3月前
|
移动开发 网络协议 算法
(十)Netty进阶篇:漫谈网络粘包、半包问题、解码器与长连接、心跳机制实战
在前面关于《Netty入门篇》的文章中,咱们已经初步对Netty这个著名的网络框架有了认知,本章的目的则是承接上文,再对Netty中的一些进阶知识进行阐述,毕竟前面的内容中,仅阐述了一些Netty的核心组件,想要真正掌握Netty框架,对于它我们应该具备更为全面的认知。
209 2
|
6月前
|
网络协议
Netty实现TCP通信
Netty实现TCP通信
100 0
|
5月前
|
消息中间件 存储 网络协议
拼多多面试:Netty如何解决粘包问题?
粘包和拆包问题也叫做粘包和半包问题,**它是指在数据传输时,接收方未能正常读取到一条完整数据的情况(只读取了部分数据,或多读取到了另一条数据的情况)就叫做粘包或拆包问题。** 从严格意义上来说,粘包问题和拆包问题属于两个不同的问题,接下来我们分别来看。 ## 1.粘包问题 粘包问题是指在网络通信中,发送方连续发送的多个小数据包被接收方一次性接收的现象。这可能是因为底层传输层协议(如 TCP)会将多个小数据包合并成一个大的数据块进行传输,导致接收方在接收数据时一次性接收了多个数据包,造成粘连。 例如以下案例,正常情况下客户端发送了两条消息,分别为“ABC”和“DEF”,那么接收端也应该收到两
40 0
拼多多面试:Netty如何解决粘包问题?
|
5月前
|
网络协议
netty粘包问题分析
netty粘包问题分析
42 0
|
5月前
|
Java
Netty传输object并解决粘包拆包问题
Netty传输object并解决粘包拆包问题
49 0
|
5月前
|
Java
Netty中粘包拆包问题解决探讨
Netty中粘包拆包问题解决探讨
36 0
|
6月前
|
网络协议 Java 物联网
Spring Boot与Netty打造TCP服务端(解决粘包问题)
Spring Boot与Netty打造TCP服务端(解决粘包问题)
1033 2