integración del resorte Boot Netty (con fuente)

prefacio

Este artículo se centra en la integración de Netty es el contenido relacionado SpringBoot Protobuf y el uso de la transmisión de datos. Protobuf simplemente introducir el uso, como en el artículo anterior Netty ha introducido simplemente, ya no hay demasiado elaborado.

protobuf

introducción

protocolbuffer (en lo sucesivo, PB) es un formato de intercambio de datos Google, es independiente del lenguaje, independiente de la plataforma. Google ofrece la realización en varios idiomas: Java, C #, C ++, ir y pitón, cada aplicación contiene un compilador y archivos de la biblioteca para el lenguaje apropiado.

Debido a que es un formato binario de utilizar el intercambio de datos XML mucho más rápido. Puede ser utilizado para el intercambio de datos en la comunicación de datos entre entornos heterogéneos o aplicaciones distribuidas. Como una excelente eficiencia y compatibilidad son formato de transmisión de datos binarios se puede utilizar en muchos campos tales como una transmisión de la red, de perfil, de almacenamiento de datos.

Dirección oficial:

https://github.com/google/protobuf

uso

Utilice para ello solamente introducir el uso de Java-relacionado. En primer lugar tenemos que crear un archivo proto, tenemos que definir la transferencia de archivos en este archivo.

Por ejemplo, tenemos que definir la información de un usuario, el campo contiene el número principal, nombre, edad.

A continuación, formatee el archivo protobuf es el siguiente:

Nota: Como se usa en este documento, es proto3, sin comentarios asociados que he escrito, no había demasiada dice la historia. Debe tenerse en cuenta que se trata de archivos proto importante y generar el nombre del archivo Java no puede ser el mismo!

syntax = "proto3";
// 生成的包名
option java_package="com.pancm.protobuf";
//生成的java名
option java_outer_classname = "UserInfo";

message UserMsg {

     // ID
     int32 id = 1;

    // 姓名
     string name = 2;

    // 年龄
      int32 age = 3;

     // 状态
     int32 state = 4;
}

Una vez creado el archivo, ponemos el archivo y protoc.exe (software archivos Java generados) poner protobuf bajo la carpeta de archivos de catálogo del disco E, y luego para entrar en la interfaz de DOS ese directorio: protoc.exe --java_out = absoluta nombre de ruta del archivo.

Por ejemplo:

protoc.exe --java_out=E:\protobuf User.proto

Una vez que haya introducido, pulse Enter para ver el bien en el mismo directorio que se ha generado archivos de Java, entonces el archivo en el archivo de proyecto en la ruta especificada.

Nota: protobuf generada archivos y archivo protobuf pruebas de software también integrados en el proyecto, y se pueden obtener directamente.

Después de que el archivo Java se genera buena, Echemos un vistazo a cómo utilizar.

Aquí sólo voy a pegar el código y los comentarios de escritura en el código, debería ser más fácil de entender algo.

Ejemplo de código:

// 按照定义的数据结构,创建一个对象
        UserInfo.UserMsg.Builder userInfo = UserInfo.UserMsg.newBuilder();
        userInfo.setId(1);
        userInfo.setName("xuwujing");
        userInfo.setAge(18);
        UserInfo.UserMsg userMsg = userInfo.build();
        // 将数据写到输出流
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        userMsg.writeTo(output);
        // 将数据序列化后发送
        byte[] byteArray = output.toByteArray();
        // 接收到流并读取
        ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
        // 反序列化
        UserInfo.UserMsg userInfo2 = UserInfo.UserMsg.parseFrom(input);
        System.out.println("id:" + userInfo2.getId());
        System.out.println("name:" + userInfo2.getName());
        System.out.println("age:" + userInfo2.getAge());

NOTA: se describe en el presente documento, porque protobuf binaria se transmite, es necesario prestar atención a la codificación correspondiente. También hay necesidad de buscar en el uso protobuf longitud máxima de una transferencia de bytes.

salida:

id:1
name:xuwujing
age:18
SpringBoot整合Netty

Nota: Si desea acceder directamente al proyecto que puede saltar directamente a la parte inferior, a través del enlace para descargar el código del proyecto.

  • Preparación para el desarrollo

  • Los requisitos ambientales

  • JDK: 1.8

  • Netty: 4,0 o más (excluyendo 5)

  • Protobuf: 3,0 o por encima

Si Netty desconocida, se puede ver en estos artículos. Gran Dios - por favor ignore. ~

https://blog.csdn.net/column/details/17640.html

En primer lugar Maven dependiente o relevante:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <netty.version>4.1.22.Final</netty.version>
        <protobuf.version>3.5.1</protobuf.version>
        <springboot>1.5.9.RELEASE</springboot>
        <fastjson>1.2.41</fastjson>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>


    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${springboot}</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${springboot}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <version>${springboot}</version>
            <optional>true</optional>
        </dependency>


        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty.version}</version>
        </dependency>

        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${protobuf.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson}</version>
        </dependency>


    <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
</dependencies>

Después de añadir el perfil pieza dependencias de Maven apropiada siendo nada se puede añadir, porque el puerto es solamente un oyente.

codificación

módulo de código se divide en servidor y el cliente.

El principal logro de la lógica de negocio:

Después de que el servidor se inicia correctamente, el cliente inicia correctamente, entonces el servidor envía un formato protobuf mensaje al cliente, y el cliente para dar la respuesta adecuada. Después de que el cliente y la conexión del servidor tiene éxito, el cliente envía un periodo de tiempo de cada comando de latido al servidor, el cliente indica al servidor se ha guardado, si el cliente no envía un mensaje, el servidor se apagará en un momento determinado y la conexión del cliente. Cuando el cliente no puede conectarse al servidor, se intentará volver a conectarse periódicamente, sólo para la reconexión!

el servidor

La primera consiste en clases de servidor del lado de escritura comienzan, las observaciones correspondientes en el código escrito muy detallado, ya no hay mucha cuenta la historia. Pero tenga en cuenta que en el artículo anterior escribí Netty, es iniciar el servidor directamente a través del método principal, y por lo tanto es un objeto de nuevos directos. Y después de SpringBoot e integración, tendremos que Netty springBoot para manejarlo, así que aquí está con los comentarios correspondientes.

Código es el siguiente:

@Service("nettyServer")
public class NettyServer {
    private static final int port = 9876; // 设置服务端端口
    private static EventLoopGroup boss = new NioEventLoopGroup(); // 通过nio方式来接收连接和处理连接
    private static EventLoopGroup work = new NioEventLoopGroup(); // 通过nio方式来接收连接和处理连接
    private static ServerBootstrap b = new ServerBootstrap();

    @Autowired
    private NettyServerFilter nettyServerFilter;


    public void run() {
        try {
            b.group(boss, work);
            b.channel(NioServerSocketChannel.class);
            b.childHandler(nettyServerFilter); // 设置过滤器
            // 服务器绑定端口监听
            ChannelFuture f = b.bind(port).sync();
            System.out.println("服务端启动成功,端口是:" + port);
            // 监听服务器关闭监听
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 关闭EventLoopGroup,释放掉所有资源包括创建的线程
            work.shutdownGracefully();
            boss.shutdownGracefully();
        }
    }
}

Después de la finalización de la preparación de la clase principal del servidor, pongamos los filtros correspondientes.

Debe herencia en Netty clase ChannelInitializer, entonces el método de anulación initchannel, añadir los ajustes apropiados, tales como el ajuste de tiempo de espera de los latidos del corazón, se proporciona el protocolo de transporte, y la clase de implementación de servicio correspondiente.

Código es el siguiente:

    @Component
     public class NettyServerFilter extends ChannelInitializer<SocketChannel> {

    @Autowired
    private NettyServerHandler nettyServerHandler;

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

         //入参说明: 读超时时间、写超时时间、所有类型的超时时间、时间格式
         ph.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
         // 解码和编码,应和客户端一致
         //传输的协议 Protobuf
         ph.addLast(new ProtobufVarint32FrameDecoder());
         ph.addLast(new ProtobufDecoder(UserMsg.getDefaultInstance()));
         ph.addLast(new ProtobufVarint32LengthFieldPrepender());
         ph.addLast(new ProtobufEncoder());

         //业务逻辑实现类
         ph.addLast("nettyServerHandler", nettyServerHandler);
       }
     }

Después de que el código de los ajustes relacionados con el servicio termine, vamos a escribir el código de negocio principal.

Netty escribir código usando la capa de negocio, tenemos que ChannelInboundHandlerAdapter hereda la clase o SimpleChannelInboundHandler, dos de ellos se distinguieron por cierto, aquí mismo.

Después de heredar la clase SimpleChannelInboundHandler, dará a conocer de forma automática los datos de recursos a cabo ByteBuffer ocupada después de recibir los datos. Y extendiendo esta necesidad clase para especificar el formato de los datos.

La herencia ChannelInboundHandlerAdapter no libera automáticamente, llamada ReferenceCountUtil.release manual () y otros métodos de liberación. Esta clase no necesita heredar un formato de datos especificado. Así que aquí, en lo personal recomiendo el lado del servicio hereda ChannelInboundHandlerAdapter, liberar manualmente, evitar que los datos no procesados ​​completamente liberados de forma automática. Y el servidor puede estar conectado a una pluralidad de clientes, y cada solicitud de cliente no coincide con el formato de datos, entonces puede ser tratada en consecuencia.

El cliente puede heredar la clase SimpleChannelInboundHandler de acuerdo a la situación. Buenos beneficios se especifican directamente transferir formato de datos, no es necesario convertir el formato.

Código es el siguiente:

@Service("nettyServerHandler")
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /** 空闲次数 */
    private int idle_count = 1;
    /** 发送次数 */
    private int count = 1;


    /**
     * 建立连接时,发送一条消息
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连接的客户端地址:" + ctx.channel().remoteAddress());
        UserInfo.UserMsg userMsg = UserInfo.UserMsg.newBuilder().setId(1).setAge(18).setName("xuwujing").setState(0)
                .build();
        ctx.writeAndFlush(userMsg);
        super.channelActive(ctx);
    }

    /**
     * 超时处理 如果5秒没有接受客户端的心跳,就触发; 如果超过两次,则直接关闭;
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
        if (obj instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) obj;
            if (IdleState.READER_IDLE.equals(event.state())) { // 如果读通道处于空闲状态,说明没有接收到心跳命令
                System.out.println("已经5秒没有接收到客户端的信息了");
                if (idle_count > 1) {
                    System.out.println("关闭这个不活跃的channel");
                    ctx.channel().close();
                }
                idle_count++;
            }
        } else {
            super.userEventTriggered(ctx, obj);
        }
    }

    /**
     * 业务逻辑处理
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("第" + count + "次" + ",服务端接受的消息:" + msg);
        try {
            // 如果是protobuf类型的数据
          if (msg instanceof UserMsg) {
                UserInfo.UserMsg userState = (UserInfo.UserMsg) msg;
                if (userState.getState() == 1) {
                    System.out.println("客户端业务处理成功!");
                } else if(userState.getState() == 2){
                    System.out.println("接受到客户端发送的心跳!");
                }else{
                    System.out.println("未知命令!");
                }
            } else {
                System.out.println("未知数据!" + msg);
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ReferenceCountUtil.release(msg);
        }
        count++;
    }

    /**
     * 异常处理
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

No es comienzo de un lado del servidor antes de comenzar las clases directamente por el método principal, pero cambió aquí fueron iniciados por springBoot, no es muy diferente.

Código es el siguiente:

@SpringBootApplication
public class NettyServerApp {

    public static void main(String[] args) {
        // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
        ApplicationContext context = SpringApplication.run(NettyServerApp.class, args);
        NettyServer nettyServer = context.getBean(NettyServer.class);
        nettyServer.run();
    }

}

Aquí está el código para el servidor apropiado en la preparación se ha completado.

cliente

En muchas partes del código del lado del cliente y el servidor son similares, no voy a extenderme demasiado, algunos de los principales a cabo bajo diferentes códigos simplemente le dice.

La primera es la clase principal del cliente, y el servidor es sustancialmente similar, es decir, más puerto de escucha y un oyente (escuchar si el servidor se desconecta, para la reconexión).

la lógica de código principal para lograr lo siguiente:

    public void doConnect(Bootstrap bootstrap, EventLoopGroup eventLoopGroup) {
        ChannelFuture f = null;
        try {
            if (bootstrap != null) {
                bootstrap.group(eventLoopGroup);
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
                bootstrap.handler(nettyClientFilter);
                bootstrap.remoteAddress(host, port);
                f = bootstrap.connect().addListener((ChannelFuture futureListener) -> {
                    final EventLoop eventLoop = futureListener.channel().eventLoop();
                    if (!futureListener.isSuccess()) {
                        System.out.println("与服务端断开连接!在10s之后准备尝试重连!");
                        eventLoop.schedule(() -> doConnect(new Bootstrap(), eventLoop), 10, TimeUnit.SECONDS);
                    }
                });
                if(initFalg){
                    System.out.println("Netty客户端启动成功!");
                    initFalg=false;
                }
                // 阻塞
                f.channel().closeFuture().sync();
            }
        } catch (Exception e) {
            System.out.println("客户端连接失败!"+e.getMessage());
        }
    }

Nota: esto se logra mediante el oyente es JDK1.8 redacción.

Filtro de su cliente y el servidor de base han sido esto. Pero tenga en cuenta que el protocolo de transporte, codificación y decodificación debe ser la misma, no debe ser menor que el tiempo de leer y escribir el tiempo establecido latidos del corazón por el servidor.

El código cambia de la siguiente manera:

   ChannelPipeline ph = ch.pipeline();
        /*
         * 解码和编码,应和服务端一致
         * */
        //入参说明: 读超时时间、写超时时间、所有类型的超时时间、时间格式
        ph.addLast(new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS));

código de lógica de negocio para el cliente.

Varios lógica principal se lleva a cabo a tiempo y resolvió enviar formato de datos latido del corazón del servicio enviado protobuf.

Aquí más de un comentario servidor, el comentario sobre todo para compartir manejador de múltiples canales puede ser la cuota de forma más segura, es para garantizar la seguridad hilo.

Sin sentido que no quiere decir, el código es el siguiente:

    @Service("nettyClientHandler")
    @ChannelHandler.Sharable
    public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    @Autowired
    private NettyClient nettyClient;

    /** 循环次数 */
    private int fcount = 1;

    /**
     * 建立连接时
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("建立连接时:" + new Date());
        ctx.fireChannelActive();
    }

    /**
     * 关闭连接时
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("关闭连接时:" + new Date());
        final EventLoop eventLoop = ctx.channel().eventLoop();
        nettyClient.doConnect(new Bootstrap(), eventLoop);
        super.channelInactive(ctx);
    }

    /**
     * 心跳请求处理 每4秒发送一次心跳请求;
     *
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
        System.out.println("循环请求的时间:" + new Date() + ",次数" + fcount);
        if (obj instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) obj;
            if (IdleState.WRITER_IDLE.equals(event.state())) { // 如果写通道处于空闲状态,就发送心跳命令
                UserMsg.Builder userState = UserMsg.newBuilder().setState(2);
                ctx.channel().writeAndFlush(userState);
                fcount++;
            }
        }
    }

    /**
     * 业务逻辑处理
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 如果不是protobuf类型的数据
        if (!(msg instanceof UserMsg)) {
            System.out.println("未知数据!" + msg);
            return;
        }
        try {

            // 得到protobuf的数据
            UserInfo.UserMsg userMsg = (UserInfo.UserMsg) msg;
            // 进行相应的业务处理。。。
            // 这里就从简了,只是打印而已
            System.out.println(
                    "客户端接受到的用户信息。编号:" + userMsg.getId() + ",姓名:" + userMsg.getName() + ",年龄:" + userMsg.getAge());

            // 这里返回一个已经接受到数据的状态
            UserMsg.Builder userState = UserMsg.newBuilder().setState(1);
            ctx.writeAndFlush(userState);
            System.out.println("成功发送给服务端!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ReferenceCountUtil.release(msg);
        }
     }
    }

Así que aquí está el código del cliente también se escribe terminado.

prueba de funcionamiento

En primer lugar, iniciar el servidor, y luego iniciar el cliente.

Si nos fijamos en los resultados como se mencionó anteriormente.

salida del servidor:

服务端启动成功,端口是:9876
连接的客户端地址:/127.0.0.1:53319
第1次,服务端接受的消息:state: 1

客户端业务处理成功!
第2次,服务端接受的消息:state: 2

接受到客户端发送的心跳!
第3次,服务端接受的消息:state: 2

接受到客户端发送的心跳!
第4次,服务端接受的消息:state: 2

接受到客户端发送的心跳!

resultados de entrada de cliente:

Netty客户端启动成功!
建立连接时:Mon Jul 16 23:31:58 CST 2018
客户端接受到的用户信息。编号:1,姓名:xuwujing,年龄:18
成功发送给服务端!
循环请求的时间:Mon Jul 16 23:32:02 CST 2018,次数1
循环请求的时间:Mon Jul 16 23:32:06 CST 2018,次数2
循环请求的时间:Mon Jul 16 23:32:10 CST 2018,次数3
循环请求的时间:Mon Jul 16 23:32:14 CST 2018,次数4

Como puede verse por los datos de impresión antes mencionado.

A continuación se analizó si el cliente puede lograr la reconexión.

En primer lugar iniciar el cliente, y luego iniciar el servidor.

resultados de entrada de cliente:

Netty客户端启动成功!
与服务端断开连接!在10s之后准备尝试重连!
客户端连接失败!AbstractChannel$CloseFuture@1fbaa3ac(incomplete)
建立连接时:Mon Jul 16 23:41:33 CST 2018
客户端接受到的用户信息。编号:1,姓名:xuwujing,年龄:18
成功发送给服务端!
循环请求的时间:Mon Jul 16 23:41:38 CST 2018,次数1
循环请求的时间:Mon Jul 16 23:41:42 CST 2018,次数2
循环请求的时间:Mon Jul 16 23:41:46 CST 2018,次数3

salida del servidor:

服务端启动成功,端口是:9876
连接的客户端地址:/127.0.0.1:53492
第1次,服务端接受的消息:state: 1

客户端业务处理成功!
第2次,服务端接受的消息:state: 2

接受到客户端发送的心跳!
第3次,服务端接受的消息:state: 2

接受到客户端发送的心跳!
第4次,服务端接受的消息:state: 2

Los resultados también se dice más arriba!

otro

Sobre la integración SpringBoot Netty Protobuf uso para la transmisión de datos a terminar aquí.

la integración SpringBoot para la transmisión de datos a través de la dirección de proyectos Protobuf Netty:

https://github.com/xuwujing/springBoot-study/tree/master/springboot-netty-protobuf

Por cierto, hay no utiliza springBoot Proyecto Netty integrada Dirección:

https://github.com/xuwujing/Netty-study/tree/master/Netty-protobuf

Publicado 50 artículos originales · ganado elogios 1706 · Vistas 2,22 millones +

Supongo que te gusta

Origin blog.csdn.net/zl1zl2zl3/article/details/105374100
Recomendado
Clasificación