Netty learning (6): Netty integra protobuf y entrega de mensajes multiprotocolo

Visión de conjunto

Fui a los búferes de protocolo de Google y di una vuelta breve y finalmente regresé. Obtuve una comprensión general del uso de protobuf. ¡Ahora vuelvo a integrarme con Netty para ver qué fragante es!

ruta de aprendizaje protobuf

Protobuf integrado de Netty

Se han escrito varias demostraciones de datos de transmisión de clientes y servidores de Netty, y tengo una comprensión clara de las rutinas de Netty: preste atención al uso de Handler en varios escenarios y reescriba el estado de cada canalización en ChannelInboundHandlerAdapter Métodos de devolución de llamada (channelRegistered, channelUnregistered, channelRead0, etc. ) ... No repetiré cómo se escriben aquí el cliente y el servidor.

Servidor:

package com.leolee.netty.sixthExample;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

import java.net.InetSocketAddress;

/**
 * @ClassName TestServer
 * @Description: 基于protobuf的服务端
 * @Author LeoLee
 * @Date 2020/9/2
 * @Version V1.0
 **/
public class TestServer {

    public static void main(String[] args) throws InterruptedException {

        //定义线程组 EventLoopGroup为死循环
        //boss线程组一直在接收客户端发起的请求,但是不对请求做处理,boss会将接收到的请i交给worker线程组来处理
        //实际可以用一个线程组来做客户端的请求接收和处理两件事,但是不推荐
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //启动类定义
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    //子处理器,自定义处理器,服务端可以使用childHandler或者handler,handlerr对应接收线程组(bossGroup),childHandler对应处理线程组(workerGroup)
                    .handler(new LoggingHandler(LogLevel.INFO))//日志处理器
                    .childHandler(new TestServerInitializer());

            //绑定监听端口
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8899)).sync();
            //定义关闭监听
            channelFuture.channel().closeFuture().sync();
        } finally {
            //Netty提供的优雅关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
package com.leolee.netty.sixthExample;

import com.leolee.protobuf.DataInfo;
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;

/**
 * @ClassName TestServerInitializer
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/2
 * @Version V1.0
 **/
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast("protobufVarint32FrameDecoder", new ProtobufVarint32FrameDecoder());
        pipeline.addLast("protobufDecoder", new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));
        pipeline.addLast("protobufVarint32LengthFieldPrepender", new ProtobufVarint32LengthFieldPrepender());
        pipeline.addLast("protobufEncoder", new ProtobufEncoder());
        pipeline.addLast(new TestServerHandler());
    }
}
package com.leolee.netty.sixthExample;

import com.leolee.protobuf.DataInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @ClassName TestServerHandler
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/2
 * @Version V1.0
 **/
public class TestServerHandler extends SimpleChannelInboundHandler<DataInfo.Student> {


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DataInfo.Student msg) throws Exception {

        System.out.println(msg.getName());
        System.out.println(msg.getAge());
        System.out.println(msg.getAddress());
    }
}

Lo principal a tener en cuenta es el siguiente código en TestServerInitializer

pipeline.addLast("protobufDecoder", new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));

Y los cambios en el tipo genérico de la clase heredada SimpleChannelInboundHandler de TestServerHandler :

public class TestServerHandler extends SimpleChannelInboundHandler<DataInfo.Student> {

}

Aquí utilicé mi aprendizaje en protobuf (3): compilar archivos .proto para generar código Java, y serializar y deserializar mensajes para generar código Java: clase DataInfo

Esta clase proporciona una serie de métodos de operación como la serialización y deserialización del mensaje del estudiante para ayudarnos a construir los datos requeridos para la transmisión de mensajes de Netty de manera extremadamente simple, y Netty también tiene un buen soporte para protobuf.

Cliente:

package com.leolee.netty.sixthExample;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * @ClassName TestClient
 * @Description: 基于protobuf的客户端
 * @Author LeoLee
 * @Date 2020/9/2
 * @Version V1.0
 **/
public class TestClient {

    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 TestClientInitializer());

            ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            //优雅关闭
            eventLoopGroup.shutdownGracefully();
        }
    }
}
package com.leolee.netty.sixthExample;

import com.leolee.protobuf.DataInfo;
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;

/**
 * @ClassName TestClientInitializer
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/2
 * @Version V1.0
 **/
public class TestClientInitializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast("protobufVarint32FrameDecoder", new ProtobufVarint32FrameDecoder());
        pipeline.addLast("protobufDecoder", new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));
        pipeline.addLast("protobufVarint32LengthFieldPrepender", new ProtobufVarint32LengthFieldPrepender());
        pipeline.addLast("protobufEncoder", new ProtobufEncoder());

        pipeline.addLast("TestClientHandler", new TestClientHandler());
    }
}
package com.leolee.netty.sixthExample;

import com.leolee.protobuf.DataInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @ClassName TestClientHandler
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/2
 * @Version V1.0
 **/
public class TestClientHandler extends SimpleChannelInboundHandler<DataInfo.Student> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DataInfo.Student msg) throws Exception {

    }

    /**
     * 功能描述: <br> 连接建立变为活跃状态后,马上向服务端写入Student message
     * 〈〉
     * @Param: [ctx]
     * @Return: void
     * @Author: LeoLee
     * @Date: 2020/9/2 21:11
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        DataInfo.Student student = DataInfo.Student
                .newBuilder()
                .setName("LeoLee")
                .setAge(25)
                .setAddress("上海")
                .build();

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

resultado de la operación:

九月 02, 2020 10:14:20 下午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x034b9076] REGISTERED
九月 02, 2020 10:14:20 下午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0x034b9076] BIND: 0.0.0.0/0.0.0.0:8899
九月 02, 2020 10:14:20 下午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x034b9076, L:/0:0:0:0:0:0:0:0:8899] ACTIVE
九月 02, 2020 10:14:30 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x034b9076, L:/0:0:0:0:0:0:0:0:8899] READ: [id: 0x054993bd, L:/127.0.0.1:8899 - R:/127.0.0.1:49587]
九月 02, 2020 10:14:30 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0x034b9076, L:/0:0:0:0:0:0:0:0:8899] READ COMPLETE
LeoLee
25
上海

Entrega de tipo de mensaje múltiple

El ejemplo anterior demuestra el análisis y la serialización de protobuf integrado de Netty, pero DataInfo.Student.getDefaultInstance () y SimpleChannelInboundHandler <DataInfo.Student> son difíciles de escribir , por lo que existe un problema de que el cliente solo puede pasar DataInfo. Mensaje de tipo de estudiante , el escenario de aplicación real, la comunicación de datos del cliente y del servidor es definitivamente diversa, Netty no proporciona una función de enrutamiento de solicitudes similar a SpringMVC, de hecho, el cliente y el servidor establecen una comunicación mantenible Canal, todos los datos deben transmitirse a través de este canal, entonces, ¿cómo debemos lidiar con esta situación real?

Hay un tipo de campo en protobuf llamada   oneof  , que se explica en la página oficial:

Si desea establecer solo un valor en un mensaje de campos múltiples al mismo tiempo, puede usar la  función oneof  para resolver este problema y ahorrar memoria.

El campo Oneof  es como otros campos modificados opcionales, excepto por la función de memoria compartida , y solo se puede configurar un campo Oneof al mismo tiempo . Al configurar cualquier campo miembro, oneof borrará otros campos . Los ejemplos son los siguientes:

message SampleMessage {
  oneof test_oneof {
     string name = 4;
     SubMessage sub_message = 9;
  }
}

Así que creemos nuestro archivo .proto .

syntax = "proto2";

package com.leolee.protobuf;

option optimize_for = SPEED;//Can be set to SPEED, CODE_SIZE, or LITE_RUNTIME,This affects the C++ and Java code generators (and possibly third-party generators) in the following ways
option java_package = "com.leolee.protobuf";
option java_outer_classname = "DataInfo2";

//生成java code 命令:protoc --java_out=src/main/java/ src/protobuf/Person.proto


//----------------多message的根节点
message DataPackage {

  optional PackageType package_type = 1;

  oneof Package {

      Student sudent = 2;
      Dog dog = 3;

  }

}

//数据包类型
enum PackageType {

    STUDENT = 0;
    DOG = 1;

}

//----------------多message
message Student {

  optional string name = 1;

  optional int32 age = 2;

  optional string address = 3;
}


message Dog {

  optional string dog_name = 1;

  optional int32 dog_age = 2;

}

Utilice el comando para generar código Java:

protoc --java_out=src/main/java/ src/protobuf/Person.proto

Para obtener un tutorial sobre la generación de código de protobuf, consulte aquí: aprendizaje de protobuf (3): compile archivos .proto para generar código Java y serialice y deserialice mensajes

Modifique el código del cliente y del servidor antes

Cliente:

package com.leolee.netty.sixthExample.multiProtocol;

import com.leolee.protobuf.DataInfo;
import com.leolee.protobuf.DataInfo2;
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;

/**
 * @ClassName TestClientInitializer
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/2
 * @Version V1.0
 **/
public class TestClientInitializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast("protobufVarint32FrameDecoder", new ProtobufVarint32FrameDecoder());
        pipeline.addLast("protobufDecoder", new ProtobufDecoder(DataInfo2.DataPackage.getDefaultInstance()));
        pipeline.addLast("protobufVarint32LengthFieldPrepender", new ProtobufVarint32LengthFieldPrepender());
        pipeline.addLast("protobufEncoder", new ProtobufEncoder());

        pipeline.addLast("TestClientHandler", new TestClientHandler());
    }
}
package com.leolee.netty.sixthExample.multiProtocol;

import com.leolee.protobuf.DataInfo2;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName TestClientHandler
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/2
 * @Version V1.0
 **/
public class TestClientHandler extends SimpleChannelInboundHandler<DataInfo2.DataPackage> {

    private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DataInfo2.DataPackage msg) throws Exception {

    }

    /**
     * 功能描述: <br> 连接建立变为活跃状态后,马上向服务端写入Student message
     * 〈〉
     * @Param: [ctx]
     * @Return: void
     * @Author: LeoLee
     * @Date: 2020/9/2 21:11
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        //一秒执行一次
        executor.scheduleAtFixedRate(() -> {
            //随机生成 0 或者 1
            int packType = new Random().nextInt(2);

            switch (DataInfo2.DataPackage.PackageType.forNumber(packType)) {
                case STUDENT:
                    System.out.println("发送student");
                    DataInfo2.DataPackage dataPackage = DataInfo2.DataPackage.newBuilder()
                            .setPackageType(DataInfo2.DataPackage.PackageType.STUDENT)
                            .setSudent(DataInfo2.Student.newBuilder()
                                    .setName("LeoLee").setAge(25).setAddress("上海").build()).build();
                    ctx.channel().writeAndFlush(dataPackage);
                    break;
                case DOG:
                    System.out.println("发送dog");
                    DataInfo2.DataPackage dataPackage2 = DataInfo2.DataPackage.newBuilder()
                            .setPackageType(DataInfo2.DataPackage.PackageType.DOG)
                            .setDog(DataInfo2.Dog.newBuilder()
                                    .setDogName("恶霸犬").setDogAge(3).build()).build();
                    ctx.channel().writeAndFlush(dataPackage2);
                    break;
            }


        }, 0, 1, TimeUnit.SECONDS);
    }



    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("客户端出现异常已关闭");
        cause.printStackTrace();
        ctx.close();
    }
}

Servidor:

package com.leolee.netty.sixthExample.multiProtocol;

import com.leolee.protobuf.DataInfo2;
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;

/**
 * @ClassName TestServerInitializer
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/2
 * @Version V1.0
 **/
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast("protobufVarint32FrameDecoder", new ProtobufVarint32FrameDecoder());
        pipeline.addLast("protobufDecoder", new ProtobufDecoder(DataInfo2.DataPackage.getDefaultInstance()));
        pipeline.addLast("protobufVarint32LengthFieldPrepender", new ProtobufVarint32LengthFieldPrepender());
        pipeline.addLast("protobufEncoder", new ProtobufEncoder());
        pipeline.addLast(new TestServerHandler());
    }
}
package com.leolee.netty.sixthExample.multiProtocol;

import com.leolee.protobuf.DataInfo2;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @ClassName TestServerHandler
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/2
 * @Version V1.0
 **/
public class TestServerHandler extends SimpleChannelInboundHandler<DataInfo2.DataPackage> {


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DataInfo2.DataPackage msg) throws Exception {

        System.out.println("msg.getPackageType().getNumber():" + msg.getPackageType().getNumber());

        //msg.PackageType.forNumber(packType)
        switch (msg.getPackageType().getNumber()) {
            case 0:
                System.out.println(msg.getSudent().getName());
                System.out.println(msg.getSudent().getAge());
                System.out.println(msg.getSudent().getAddress());
                break;
            case 1:
                System.out.println(msg.getDog().getDogName());
                System.out.println(msg.getDog().getDogAge());
                break;
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("服务端出现异常已关闭");
        cause.printStackTrace();
        ctx.close();
    }
}

Los resultados de la implementación no se publicarán, y siéntelos por ti mismo.

Aquellos que necesitan código vienen aquí para obtenerlo: dirección del proyecto de demostración

Continuará, ¿cuántos controladores integrados no explicaron?

Supongo que te gusta

Origin blog.csdn.net/qq_25805331/article/details/108371049
Recomendado
Clasificación