04.netty中使用protobuf(多协议)

一、Protobuf多协议消息支持

1、思路一:netty官方给出“利用netty提供的自定义协议方式”

在传递消息时,会用消息的前两几位(官方例子中是前2位)代表消息的类型,比如AB、CD、EF是三种不同的消息

解码器解析时,解析前两位,并根据结果对应不同的消息类型。这种方案需要自己实现自定义解码器(具体可以参考netty的官网示例中有一个例子:

netty-source-java\src\io\netty\example\portunification、

评价:此种解决方案相对来说比较复杂,没有通过protobuf的特性来解决,而是通过netty对自定义协议进行了很好的支持的方案。

2、思路二:也是老师实际工作中经常使用的,扩展性、可读性更好一些:”通过消息的定义方式(在protobuf的IDL文件中语法特性)“

错误的示例:

因为在Server和Client的handler中的范型只能是一种类型,如果按照下面方式定义的IDL,只能声明其中一种消息类型

正确的示例:

原理:在IDL最外层只定义一个消息类型“包含”(通过oneof)所有消息类型(这里的包含指的是属性上的包含,而不是类定义位置上的包含),这里的所有消息类型包括所有可能出现的消息类(客户端向服务器发送/返回的+服务器向客户端发送/返回的)然后通过  枚举  来判断每次消息传递的类型。

Person2.proto

syntax = "proto2";

package com.shengsiyuan.sixthexample.multyMsgType;

option optimize_for = SPEED;
option java_package = "com.mzj.netty.ssy._06.protobuf.netty.multyMsgType";
option java_outer_classname = "MyDataInfo";

message MyMessage{//MyMessage是最外层的消息

                  //通过枚举区分不同的消息类型
                  enum DataType{
                      PersionType = 1;
                      DogType = 2;
                      CatType = 3;
                  }
                  //枚举类型是required类型,用来标识oneof中的类型
                  required DataType data_type = 1;

                  //oneof相当于一个包裹块
                  //oneof中成员共享内存
                  //用oneof包裹的属性用来表示其中的对象只能出现一个,要么Person要么Dog要么Cat
                  //后设置的会顶替前设置的
                  oneof dataBody{//其中内部包括其他消息,dataBody的名字不会起作用
                                 Person person = 2;
                                 Dog dog = 3;
                                 Cat cat = 4;
                  }
}

message Person {
    optional string name = 1;
    optional int32 age = 2;
    optional string address = 3;
}

message Dog {
    optional string name = 1;
    optional int32 age = 2;
}

message Cat {
    optional string name = 1;
    optional string city = 2;
}

其中,消息每次传递都是最外层的MyMessage类型,并且消息中data_type属性是必须字段,根据其枚举的取值对应不同的消息(内部数据)类型。消息的构造者(使用者)需要保证每次发送的消息中枚举类型与oneof中的对象对应,同时保证Person、Dog、Cat在每个消息中只有一个。

oneof官方文档中说明:

3、编译IDL:protoc --java_out=src/main/java/ src/main/resources/protobuf/netty/Person2.proto

4、编写代码

1)TestServer.java

无变化,略

2)TestServerInitializer.java

package com.mzj.netty.ssy._06.protobuf.netty.multyMsgType;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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;

public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipline = ch.pipeline();

        pipline.addLast(new ProtobufVarint32FrameDecoder());
        pipline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));//修改了消息实例的类型
        pipline.addLast(new ProtobufVarint32LengthFieldPrepender());
        pipline.addLast(new ProtobufEncoder());

        pipline.addLast(new TestServerHandler());
    }
}

注意修改了消息实例的类型

3)TestServerHandler.java

package com.mzj.netty.ssy._06.protobuf.netty.multyMsgType;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

//范型类型:服务端与客户端传递的数据对象类型实例,即编解码类型
public class TestServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {

        MyDataInfo.MyMessage.DataType dataType = msg.getDataType();

        if(dataType == MyDataInfo.MyMessage.DataType.PersionType){
            MyDataInfo.Person person = msg.getPerson();

            System.out.println("[服务端收到]"+person.getName());
            System.out.println("[服务端收到]"+person.getAge());
            System.out.println("[服务端收到]"+person.getAddress());
        }else if (dataType == MyDataInfo.MyMessage.DataType.DogType){
            MyDataInfo.Dog dog = msg.getDog();

            System.out.println("[服务端收到]"+dog.getName());
            System.out.println("[服务端收到]"+dog.getAge());
        }else if (dataType == MyDataInfo.MyMessage.DataType.CatType){
            MyDataInfo.Cat cat = msg.getCat();

            System.out.println("[服务端收到]"+cat.getName());
            System.out.println("[服务端收到]"+cat.getCity());
        }

        MyDataInfo.MyMessage myMessage = null;
        //模拟发送不同消息类型
        myMessage =  MyDataInfo.MyMessage.newBuilder().
                setDataType(MyDataInfo.MyMessage.DataType.PersionType).
                setPerson(MyDataInfo.Person.newBuilder().
                        setName("mazhongjia").
                        setAge(34).
                        setAddress("beijing").build()).
                build();


        ctx.channel().writeAndFlush(myMessage);

    }

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

4)TestClient.java

无变化,略

5)TestClientInitializer.java

package com.mzj.netty.ssy._06.protobuf.netty.multyMsgType;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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;

/**
 * Channel初始化器
 */
public class TestClientInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipline = ch.pipeline();

        pipline.addLast(new ProtobufVarint32FrameDecoder());
        pipline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));//注意解码消息类型的变化
        pipline.addLast(new ProtobufVarint32LengthFieldPrepender());
        pipline.addLast(new ProtobufEncoder());

        pipline.addLast(new TestClientrHandler());
    }
}

修改内容与服务端相同:消息类型

6)TestClientrHandler.java

package com.mzj.netty.ssy._06.protobuf.netty.multyMsgType;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class TestClientrHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {//范型类型的变化


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {
        MyDataInfo.MyMessage.DataType dataType = msg.getDataType();

        if(dataType == MyDataInfo.MyMessage.DataType.PersionType){
            MyDataInfo.Person person = msg.getPerson();

            System.out.println("[客户端收到]"+person.getName());
            System.out.println("[客户端收到]"+person.getAge());
            System.out.println("[客户端收到]"+person.getAddress());
        }else if (dataType == MyDataInfo.MyMessage.DataType.DogType){
            MyDataInfo.Dog dog = msg.getDog();

            System.out.println("[客户端收到]"+dog.getName());
            System.out.println("[客户端收到]"+dog.getAge());
        }else if (dataType == MyDataInfo.MyMessage.DataType.CatType){
            MyDataInfo.Cat cat = msg.getCat();

            System.out.println("[客户端收到]"+cat.getName());
            System.out.println("[客户端收到]"+cat.getCity());
        }
    }

    /**
     * 当client连接成功时,由client发起请求
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        MyDataInfo.MyMessage myMessage = null;
        //模拟发送不同消息类型
            myMessage =  MyDataInfo.MyMessage.newBuilder().
                    setDataType(MyDataInfo.MyMessage.DataType.PersionType).
                    setPerson(MyDataInfo.Person.newBuilder().
                            setName("mazhongjia").
                            setAge(34).
                            setAddress("beijing").build()).
                    build();


        ctx.channel().writeAndFlush(myMessage);
    }
}

netty多协议的评价

为什么netty的多协议,与springmvc、springboot的url路由处理多协议相比没有这么优雅呢?后者通过不同url的方式清晰的区分不同协议,而前者需要对不同类型的协议进行if...else...判断。其实url路由也是需要dispatch的(Spring mvc中的DispatcherServlet),只不过被封装了、本质上是相同用的。在启动web框架时,会根据注解也好、配置文件也好,进行解析,生成对应url与处理器的映射关系。

同时,netty相比其他web框架更底层,不会提供url路由这种高级框架功能,如果基于netty开发,类似的URL路由完全可以自己实现、自己封装。(netty开发http服务端时,URL路由也需要自己实现)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值