Netty使用Protobuf編解碼

Protocol Buffers介紹

Protocol Buffers(Protobuf)是Google推出的語言無關,平臺無關的可擴展機制沦泌,用于序列化結(jié)構(gòu)數(shù)據(jù)。由于是一種二進制的序列化協(xié)議餐曹,并且數(shù)據(jù)結(jié)構(gòu)做了相應優(yōu)化摆屯,因此比傳統(tǒng)的XML、JSON數(shù)據(jù)格式序列化后體積更小诗良,傳輸更快汹桦。Protobuf有proto2和proto3兩個版本,有著不同的數(shù)據(jù)格式定義鉴裹,在這里我們使用proto3版本舞骆。Protobuf使用了一個編譯器,可根據(jù).proto文件中定義的數(shù)據(jù)結(jié)構(gòu)径荔,自動生成各種語言的模板代碼督禽,方便使用。

一個hello world示例程序

我們以一個hello world的例子來嘗試在Netty中使用Protobuf:編寫一個GreetClient总处,向服務端發(fā)送用戶姓名name(如world)狈惫;服務端接收到請求,在姓名name前面添加前綴hello(如name為hello鹦马,添加前綴變?yōu)?code>hello word)胧谈,并將該消息message返回給客戶端。這個例子參考了grpc官方的hello world例子荸频。

編寫helloworld.proto文件

首先我們編寫一個helloworld.proto文件菱肖,內(nèi)容如下:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.alphabc.netty.protobuf.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The request message containing the user's name.
message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}

編譯生成Java代碼

Protobuf的官方github release頁面下載對應操作系統(tǒng)上需要用到的編譯器文件,并解壓得到protoc试溯。

為了使用protoc將helloworld.proto文件編譯成Java代碼蔑滓,進入項目目錄執(zhí)行以下命令:

protoc --java_out=src/main/java  src/main/java/com/alphabc/netty/protobuf/helloworld/helloworld.proto

最終生成的類目錄結(jié)構(gòu)大致如下:

src/main/java/com/alphabc/netty/protobuf/helloworld
            ├── HelloReply.java
            ├── HelloReplyOrBuilder.java
            ├── HelloRequest.java
            ├── HelloRequestOrBuilder.java
            ├── HelloWorldProto.java
            └── helloworld.proto

由于生成的模板代碼較多,此處就不貼出來了。

引入Maven依賴

引入Netty和Protobuf的Maven依賴如下:

    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.32.Final</version>
    </dependency>
    <dependency>
      <groupId>com.google.protobuf</groupId>
      <artifactId>protobuf-java</artifactId>
      <version>3.11.0</version>
    </dependency>

測試Protobuf的序列化和反序列化

編寫Protobuf的序列化和反序列化測試類如下:

package com.alphabc.netty.protobuf;

import com.alphabc.netty.protobuf.helloworld.HelloRequest;
import com.google.protobuf.InvalidProtocolBufferException;

public class ProtobufTest {

    public static void main(String[] args) throws InvalidProtocolBufferException {
        HelloRequest helloRequest = HelloRequest.newBuilder().setName("Jack").build();
        byte[] bytes = helloRequest.toByteArray();
        HelloRequest parse = HelloRequest.parseFrom(bytes);
        System.out.println(parse.toString());
        System.out.println(parse.getName());
    }
}

執(zhí)行后控制臺輸出:

name: "Jack"

Jack

編寫服務端和客戶端代碼

編寫Netty服務端GreetServer代碼:

package com.alphabc.netty.protobuf;

import com.alphabc.netty.protobuf.helloworld.HelloRequest;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;

import java.net.InetSocketAddress;

public class GreetServer {

    private final int port;

    public GreetServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println("Usage: " + GreetServer.class.getSimpleName() + " <port>");
            return;
        }
        int port = Integer.parseInt(args[0]);
        new GreetServer(port).start();
    }

    public void start() throws Exception {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new ProtobufVarint32FrameDecoder())
                                    .addLast(new ProtobufDecoder(HelloRequest.getDefaultInstance()))
                                    .addLast(new ProtobufVarint32LengthFieldPrepender())
                            .addLast(new ProtobufEncoder())
                            .addLast(new GreetServerHandler());
                        }
                    });
            ChannelFuture f = b.bind().sync();
            System.out.println(GreetServer.class.getName() + " started and listen on " + f.channel().localAddress());
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully().sync();
            workerGroup.shutdownGracefully().sync();
        }
    }
}

服務端處理器GreetServerHandler代碼如下:

package com.alphabc.netty.protobuf;

import com.alphabc.netty.protobuf.helloworld.HelloReply;
import com.alphabc.netty.protobuf.helloworld.HelloRequest;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class GreetServerHandler extends SimpleChannelInboundHandler<HelloRequest> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HelloRequest msg) throws Exception {
        System.out.println("request: " + msg.getName());
        HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + msg.getName()).build();
        ChannelFuture future = ctx.writeAndFlush(reply);
        future.addListener(ChannelFutureListener.CLOSE);
    }
}

Netty客戶端代碼:

package com.alphabc.netty.protobuf;

import com.alphabc.netty.protobuf.helloworld.HelloReply;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;

import java.net.InetSocketAddress;

public class GreetClient {

    private final String host;
    private final int port;

    public GreetClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 2) {
            System.err.println("Usage: " + GreetClient.class.getSimpleName() + " <host> <port>");
            return;
        }

        final String host = args[0];
        final int port = Integer.parseInt(args[1]);

        new GreetClient(host, port).start();
    }

    private void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new ProtobufVarint32FrameDecoder())
                            .addLast(new ProtobufDecoder(HelloReply.getDefaultInstance()))
                                    .addLast(new ProtobufVarint32LengthFieldPrepender())
                                    .addLast(new ProtobufEncoder())
                                    .addLast(new GreetClientHandler());
                        }
                    });
            ChannelFuture f = b.connect().sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }
}

客戶端處理器GreetClientHandler

package com.alphabc.netty.protobuf;

import com.alphabc.netty.protobuf.helloworld.HelloReply;
import com.alphabc.netty.protobuf.helloworld.HelloRequest;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class GreetClientHandler extends SimpleChannelInboundHandler<HelloReply> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HelloReply msg) throws Exception {
        System.out.println("Greeting: " + msg.getMessage());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        HelloRequest request = HelloRequest.newBuilder().setName("world").build();
        ctx.writeAndFlush(request);
    }
}

代碼測試

首先啟動服務端键袱,監(jiān)聽于5000端口燎窘,再啟動客戶端進行測試。

服務端控制臺輸出如下:

com.alphabc.netty.protobuf.GreetServer started and listen on /0:0:0:0:0:0:0:0:5000
request: world

客戶端控制臺輸出如下:

Greeting: Hello world

總結(jié)

hello world示例程序可以看出蹄咖,Netty已經(jīng)為我們內(nèi)置相應的Protobuf編解碼器褐健,編寫一個使用Protobuf協(xié)議的服務端和客戶端還是相對簡單的,但是要寫好還是有很多事情要處理澜汤,如異常處理和支持多個Protobuf消息等等問題蚜迅。

參考資料

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市俊抵,隨后出現(xiàn)的幾起案子谁不,更是在濱河造成了極大的恐慌,老刑警劉巖徽诲,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刹帕,死亡現(xiàn)場離奇詭異,居然都是意外死亡谎替,警方通過查閱死者的電腦和手機偷溺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钱贯,“玉大人挫掏,你說我怎么就攤上這事≈让” “怎么了尉共?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長硫麻。 經(jīng)常有香客問我爸邢,道長樊卓,這世上最難降的妖魔是什么拿愧? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮碌尔,結(jié)果婚禮上浇辜,老公的妹妹穿的比我還像新娘。我一直安慰自己唾戚,他們只是感情好柳洋,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著叹坦,像睡著了一般熊镣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天绪囱,我揣著相機與錄音测蹲,去河邊找鬼。 笑死鬼吵,一個胖子當著我的面吹牛扣甲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播齿椅,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼琉挖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涣脚?” 一聲冷哼從身側(cè)響起示辈,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎遣蚀,沒想到半個月后顽耳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡妙同,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年射富,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粥帚。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡胰耗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出芒涡,到底是詐尸還是另有隱情柴灯,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布费尽,位于F島的核電站赠群,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏旱幼。R本人自食惡果不足惜查描,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柏卤。 院中可真熱鬧冬三,春花似錦、人聲如沸缘缚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桥滨。三九已至窝爪,卻和暖如春弛车,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒲每。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工帅韧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人啃勉。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓忽舟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親淮阐。 傳聞我的和親對象是個殘疾皇子叮阅,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容