1. 程式人生 > >Netty使用Google Protocol Buffer完成伺服器高效能資料傳輸

Netty使用Google Protocol Buffer完成伺服器高效能資料傳輸

一、什麼是Google Protocol Buffer(protobuf官方網站)

下面是官網給的解釋:
Protocol buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data. – think XML, but smaller, faster, and simpler.
協議緩衝區是一種和語言無關、平臺無關的可擴充套件機制,用於序列化結構化的資料。相比於xml,它更小,更快,更簡單。資料緩衝區常用語通訊協議和資料儲存。

二、ProtoBuf的效能

序列化測試對比:
Ser Time + Deser Time(ns)

下面兩個網站是效率測試實驗:

  • https://code.google.com/archive/p/thrift-protobuf-compare/wikis/Benchmarking.wiki
  • https://github.com/eishay/jvm-serializers/wiki

三、使用Intellij IDEA外掛自動生成Java類

參考我的另一篇文章:Google Protocol Buffer 的使用(一)

四、Netty和protobuf整合

準備我們的proto檔案

syntax = "proto3";
package com.netty.protobuf;
option java_outer_classname = "UserInfoProto";

//使用者資訊
message UserInfo{
    //姓名
    string name = 1;
    //住址
    repeated Address address= 2;
    //年齡
    uint32 age = 3;
}

//使用者常用住址
message Address{
    string addressname = 1;
    uint32 adressno = 2;
}

伺服器中設定protobuf編碼器和解碼器

@Override
protected void initChannel(SocketChannel ch) throws Exception {
    ChannelPipeline pipeline = ch.pipeline();
    //protobuf解碼器
    pipeline.addLast(new ProtobufVarint32FrameDecoder());
    pipeline.addLast(new ProtobufDecoder(UserInfoProto.UserInfo.getDefaultInstance()));
    //protobuf編碼器
    pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
    pipeline.addLast(new ProtobufEncoder());
    pipeline.addLast(new NettyServerHandler());
}
  • ProtobufVarint32FrameDecoder是protobuf方式的解碼器,用於解決TCP粘包和拆包問題
  • ProtobufDecoder 中設定我們的proto檔案生成的例項,其實就是我們的目標Java類,設定方式為:UserInfoProto.UserInfo.getDefaultInstance()
  • ProtobufVarint32LengthFieldPrepender和ProtobufEncoder是protobuf方式的編碼器

處理類中寫protobuf資料

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        LOGGER.info("client {} connected.", ctx.channel().remoteAddress());
        UserInfoProto.UserInfo user = UserInfoProto.UserInfo.newBuilder()
                .setName("server")
                .setAge(18)
                .addAddress(
                        UserInfoProto.Address.newBuilder()
                                .setAddressname("beijing 001")
                                .setAdressno(911))
                .build();
        ctx.writeAndFlush(user);
    }
  • 這裡通過UserInfoProto.UserInfo.newBuilder()使用的時間其類的建造者模式設定使用者相關資訊。
  • 再通過ChannelHandlerContext的writeAndFlush方法寫使用者資料。

處理器中讀protobuf資料

    private int count = 0;
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        UserInfoProto.UserInfo message = (UserInfoProto.UserInfo) msg;
        LOGGER.info("server received message {}:{}", ++count, message);
    }
  • channelRead中的Object物件通過解碼之後就是一個protobuf類物件,所以可以強轉:UserInfoProto.UserInfo message = (UserInfoProto.UserInfo) msg;

五、注意事項(TCP讀半包處理)

這裡我們只是簡單使用了netty自帶的ProtobufVarint32FrameDecoder解碼器來處理讀半包問題,我們還可以自己繼承ByteToMessageDecoder類實現一個定製化的解碼器。比如我們使用Java客戶端和C++伺服器通過protobuf協議來通訊時,就需要自己實現,同時還需要考慮大端、小端模式的轉換問題