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…


相关文章
|
4月前
|
编解码 缓存 移动开发
TCP粘包/拆包与Netty解决方案
TCP粘包/拆包与Netty解决方案
45 0
|
7月前
|
移动开发 网络协议 算法
由浅入深Netty粘包与半包解决方案
由浅入深Netty粘包与半包解决方案
50 0
|
2月前
|
网络协议
Netty实现TCP通信
Netty实现TCP通信
43 0
|
12天前
|
网络协议 Java 物联网
Spring Boot与Netty打造TCP服务端(解决粘包问题)
Spring Boot与Netty打造TCP服务端(解决粘包问题)
26 1
|
3月前
|
编解码
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战_自定义长度分包编解码码器
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战_自定义长度分包编解码码器
46 0
|
3月前
|
网络协议
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战
49 0
|
4月前
|
存储 网络协议 算法
Netty使用篇:半包&粘包
Netty使用篇:半包&粘包
|
8月前
|
Nacos
Netty自定义消息协议的实现逻辑处理粘包拆包、心跳机制
Netty自定义消息协议的实现逻辑处理粘包拆包、心跳机制
106 0
|
8月前
|
存储 编解码 网络协议
Netty各组件基本用法、入站和出站详情、群聊系统的实现、粘包和拆包
Netty各组件基本用法、入站和出站详情、群聊系统的实现、粘包和拆包
70 0
|
9月前
|
网络协议 算法
Netty入门到超神系列-TCP粘包拆包处理
TCP是面向连接的,服务端和客户端通过socket进行数据传输,发送端为了更有效的发送数据,通常会使用Nagle算法把多个数据块合并成一个大的数据块,这样做虽然提高了效率,但是接收端就很难识别完整的数据包了(TCP无消息保护边界),可能会出现粘包拆包的问题。
65 0