基於Netty實現私有化協議(序列化數據結構協議ProtoBuf)

因爲項目需要,需要自定義通信協議。序列化協議使用到了Google的ProtoBuf,這裏也是通過一個案例來實現基於Netty的私有化協議的開發。

protoBuf 介紹

Google Protocol Buffer(protoBuf)是一種平臺無關,語言無關,可擴展且輕便高效的序列化數據結構的協議,
相比傳統的XML、JSON序列化的方式,更小、序列化更快、傳輸速度更快。只需要定義一次你的結構化文件,就可以自己
生成源代碼,實現輕鬆的讀寫結構化文件。並且可以使用各種語言.

優點

  • 跨平臺、跨語言,支持Java、Python、C++、Go、JavaScript;
  • 序列化更快(比xml、json方式提升20~100倍);
  • 代碼自動生成,生成不同語言代碼;

缺點

  • 通用性較差,沒有json、xml普及;
  • 數據結構化沒有json、xml表達能力那麼強;
  • 以二進制數據流方式存儲(不可讀),需要通過.proto文件 才能瞭解到數據結構;

總結:protoBuf更側重數據序列化,應用場景更明確,xml、json的應用場景更爲豐富。

protoBuf 語法

這裏就不一一介紹語法,後面會開專門章節來講,直接貼上一段定義好的.proto消息文件。

syntax = "proto2";
package com.elisland.customprotocol;
option optimize_for = SPEED; //文件屬性
option java_package = "com.elisland.customprotocol";
option java_outer_classname = "CustomMessageData";

message MessageData{
    required int64 length = 1;
    optional Content content = 2;
    enum DataType {
        REQ_LOGIN = 0;  //上線登錄驗證環節 等基礎信息上報
        RSP_LOGIN = 1;  //返回上線登錄狀態與基礎信息
        PING = 2;  //心跳
        PONG = 3;  //心跳
        REQ_ACT = 4;  //動作請求
        RSP_ACT = 5;  //動作響應
        REQ_CMD = 6;  //指令請求
        RSP_CMD = 7;  //指令響應
        REQ_LOG = 8 ;//日誌請求
        RSP_LOG = 9;  //日誌響應
    }
    optional DataType order = 3;
    message Content{
        optional int64 contentLength = 1;
        optional string data = 2;
    }
}

文件生成,這裏使用Java語言生成,java文件。語法 protoc --java_out=生成java文件位置 .proto文件位置
代碼生成完成後,接下來就是基於Netty來傳輸protoBuf協議。

Netty 使用

Netty簡介:高效的Java NIO框架,通過對傳統NIO的封裝,提供更加便捷高效的非阻塞式開發。 後面會開具體的章節來講解Netty的學習。本次案例,通過Netty構建客戶端、服務端之間的數據傳輸、以及通過我們自定義編解碼器的方式來解析和編碼我們使用的.protoBuf消息。閒話少說,後面直接上代碼。

服務器Server端

服務端
public class MyServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();//接收連接,將連接發送給worker
        EventLoopGroup workerGroup = new NioEventLoopGroup();//處理連接
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new MyDecode());
                            pipeline.addLast(new MyEncode());
                            pipeline.addLast(new MyServerHandle());
                        }
                    });
            ChannelFuture sync = serverBootstrap.bind(8899).sync();
            sync.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();//關閉
            workerGroup.shutdownGracefully();
        }
    }
}
自定義編碼器
public class MyEncode extends MessageToByteEncoder<CustomMessageData.MessageData> {

    /**
     * 編碼器
     * @param ctx
     * @param msg
     * @param out
     * @throws Exception
     */
    @Override
    protected void encode(ChannelHandlerContext ctx, CustomMessageData.MessageData msg, ByteBuf out) throws Exception {
        System.out.println("MyEncode invoke...");
        out.writeInt(((int) (msg.getLength())));
        out.writeByte((byte)msg.getOrder().getNumber());
        out.writeBytes(msg.getContent().getData().getBytes());
    }
}
自定義解碼器
public class MyDecode extends ReplayingDecoder {
    /**
     * 解碼器
     * @param ctx
     * @param in
     * @param out
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("Mydecode invoke...");
        int length = in.readInt();
        byte order = in.readByte();
        byte[] content = new byte[length];
        in.readBytes(content);

        CustomMessageData.MessageData decodeData = CustomMessageData.MessageData.newBuilder()
                .setLength(length)
                .setOrder(CustomMessageData.MessageData.DataType.forNumber(order))
                .setContent(CustomMessageData.MessageData.Content.newBuilder()
                        .setData(new String(content, Charset.forName("utf-8")))).build();
         out.add(decodeData);
    }
}
服務端Handler
public class MyServerHandle extends SimpleChannelInboundHandler<CustomMessageData.MessageData> {
    int count;//記錄接收數據數量

    /**
     * 接受client發送來的消息
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, CustomMessageData.MessageData msg) throws Exception {
        long length = msg.getLength();
        CustomMessageData.MessageData.DataType order = msg.getOrder();
        String ContentData = msg.getContent().getData();
        System.out.println("服務端接收到的數據長度:" + length);
        System.out.println("服務端接收到的數據指令:" + order);
        System.out.println("服務端接收到的數據內容:" + ContentData);
        System.out.println("服務端接收到的數據數量:" + (++count));

        String sendClientMessage = UUID.randomUUID().toString();
        int sendClientMessageLength = sendClientMessage.getBytes("utf-8").length;
        //每次收到客戶端消息後,向客戶端返回UUID字符串
        CustomMessageData.MessageData message = CustomMessageData.MessageData.newBuilder()
                .setLength(sendClientMessageLength)
                .setOrder(CustomMessageData.MessageData.DataType.forNumber(1))
                .setContent(CustomMessageData.MessageData.Content.newBuilder().setData(sendClientMessage)).build();
        ctx.writeAndFlush(message);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        ctx.close();
    }
}

服務器Client端

客戶端
public class MyClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new MyDecode());
                            pipeline.addLast(new MyEncode());
                            pipeline.addLast(new MyClientHandle());
                        }
                    });

            ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}
客戶端handler
public class MyClientHandle extends SimpleChannelInboundHandler<CustomMessageData.MessageData> {
    int count;

    /**
     * 客戶端和服務端建立連接後,向服務端發送消息
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            String message = "message for client";
            int length = message.getBytes().length;
            MessageProtocol messageProtocol = new MessageProtocol();
            messageProtocol.setLength(length);
            messageProtocol.setContent(message);
            //構建消息內容
            CustomMessageData.MessageData messageData = CustomMessageData.MessageData.newBuilder()
                    .setLength(length)
                    .setOrder(CustomMessageData.MessageData.DataType.forNumber(1))
                    .setContent(CustomMessageData.MessageData.Content.newBuilder().setData(message)).build();
            ctx.writeAndFlush(messageData);
        }
    }

    /**
     * 接受響應服務端消息
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, CustomMessageData.MessageData msg) throws Exception {
        long length = msg.getLength();
        CustomMessageData.MessageData.DataType order = msg.getOrder();
        String data = msg.getContent().getData();
        System.out.println("客戶端接收到的數據長度:" + length);
        System.out.println("客戶端接收到的數據指令:" + order);
        System.out.println("客戶端接收到的數據內容:" + data);
        System.out.println("客戶端接收到的數據數量:" + (++count));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        ctx.close();
    }
}

* .protoBuf生成的java文件未貼出。
流程:服務端啓動後,監聽客戶端,當客戶端啓動完成後,客戶端handler中的channelActive()方法會連續向服務器端發送10條消息。在傳輸過程中,先經過自定義的Encode編碼器,服務器端接受到發送過來的消息時,再通過自定義解碼器去解析消息。服務器向客戶端返回應答消息(UUID字符串),同樣是通過編碼器將消息編碼,客戶端收到消息後再通過解碼器將消息解析打印。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章