Un inicio rápido con el marco de red de alto rendimiento netty

1. ¿Qué es netty?

Antes de usar netty, primero debemos saber cuáles son las ventajas de netty y qué problemas puede resolver.

También podríamos citar directamente el contenido principal del sitio web oficial de netty:

imagen.png

La traducción es: netty es un marco de programación de red impulsado por eventos asincrónicos que admite el desarrollo rápido de servidores y clientes orientados a protocolos de alto rendimiento que se pueden mantener.

Tenga en cuenta las palabras clave en negrita arriba:

  • La compatibilidad con la asincronía es un soporte importante para la eficiencia de Netty. En la API de Java tradicional, necesitamos una codificación más engorrosa para usar NIO, pero Netty encapsula una API fácil de usar para nosotros, e incluso podemos convertir todo el servicio de OIO a NIO. con una línea de código!
  • Impulsado por eventos se refiere a la forma en que Netty maneja los mensajes de red. El marco nos proporciona una interfaz/clase abstracta completa. Cada método representa varios eventos que ocurren durante la transmisión de la red. En nuestro proceso de codificación, solo necesitamos mejorar Cada método puede manejar todos tipos de comunicación en red!
  • La velocidad del desarrollo rápido no se debe solo a que netty proporciona una lógica de marco adecuada para procesar la comunicación de la red, sino también a que proporciona una gran cantidad de códecs maduros en su interior. Solo necesitamos agregarlos a nuestra tubería para manejar fácilmente cosas como HTTP/HTTPS/Websocket /IMAP/ProtoBuf y otros protocolos
  • Hay muchas razones para un alto rendimiento sobre netty, que incluyen, entre otras, la compatibilidad con NIO, cero copia, optimizaciones de subprocesos múltiples, etc. Y solo necesitamos usar netty framework para disfrutar fácilmente del poderoso rendimiento que nos brindaron nuestros predecesores.

Presentaremos netty desde los siguientes aspectos:

  • Flujo de trabajo de netty: obtenga una comprensión general de cómo netty maneja la información de la red
  • ChannelHandler: un importante componente de procesamiento de información de red en netty
  • Códec: como ChannelHandler especial, proporciona a los usuarios un análisis de protocolo conveniente
  • EventLoop: Netty distribuye eventos internamente según el modelo Reactor
  • ByteBuf: contenedor de bytes eficiente y fácil de usar proporcionado por netty
  • ChannelFuture: El modelo de futuro definido por la propia netty, utilizado al realizar operaciones asíncronas
  • Arranque del cliente y del servidor: el paso final, cómo iniciar nuestro servicio de red

2. Flujo de trabajo de netty

Para comenzar con netty, puede comprender el flujo de trabajo de netty en una imagen:

canal.png

A continuación te explicamos este cuadro:

  • Canal: Al igual que se hace en JavaNIO, netty abstrae el funcionamiento de la red IO en un canal, por lo que ya no operamos el socket básico, sino el canal encapsulado.
  • pipline: los eventos que fluyen desde la canalización hasta netty deben procesarse a través de ChannelHandlers pesados ​​ChannelHandler es el marco proporcionado por netty y los codificadores gastan la mayor parte de su energía. Una tubería es una abstracción que conecta secuencialmente ChannelHandlers.
  • ChannelHandler: como se mencionó anteriormente, ChannelHandler es el cuerpo principal para que procesemos la información de la red. El ChannelHandler que codificamos hereda del ChannelHandler definido por netty, y sus diversos métodos se utilizan para responder a diferentes eventos, que también es el principal impulsor de netty. eventos reflexionar

3, controlador de canales

concepto

ChannelHandler se divide en dos categorías: ChannelInboundHandler y ChannelOutboundHandler. Los eventos entrantes y salientes en la figura anterior se manejan respectivamente, razón por la cual muchos autores describen la relación entre pipline y ChannelHandler de la siguiente manera (red de origen de la imagen):

imagen.png

De hecho, tal imagen es fácil de engañar a los lectores, pensando que ChannelInboundHandler y ChannelOutboundHandler están en dos líneas diferentes.

而实际它们都串联在pipline上,只是在流经时会对是处理入站事件的handler还是出站事件的handler加以判别。例如入站事件遇到了出站事件的ChannelOutboundHandler会直接跳过。

实践

通常我们继承的ChannelHandler来自下图:

imagen.png

  • ChannelHandler是公共的抽象父类,我们通常不会使用。
  • ChannelInboundHandler与ChannelOutboundHandler接口中定义了netty中各种事件,是事件驱动的规范,通过继承它们我们可以实现自己的ChannelHandler。
  • ChannelHandlerAdapter为我们封装了接口的简单实现,因此继承它们我们不用为每个方法编写自己的实现,只需要将注意力集中在我们所需要的事件驱动方法上。

一个简单的继承于SimpleChannelInboundHandler(其是ChannelInboundHandlerAdapter的子类,帮助我们在处理完消息后释放消息的内存引用,这是ChannelInboundHandlerAdapter不会做到的,因此我们也通常继承它)的Handler如下所示:

public class EchoClientHandler extends  SimpleChannelInboundHandler<ByteBuf>{
    
    //当channel连接事件发生时的响应
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rock!",CharsetUtil.UTF_8));
    }
    
    //当channel读入数据事件发生时的响应
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf in) throws Exception {
        System.out.println("Client received:"+in.toString(CharsetUtil.UTF_8));
    }
}
复制代码

4、编解码器

概念

编解码器其实就是一类特殊的ChannelHandler,以MessageToMessageDecoder为例,其继承关系如下:

imagen.png

而往往为一类事物中的某种专门抽象出子类,代表着其必然有着特殊的意义。

在网络通信中,我们要传递信息往往需要协议的封装,而协议通常是规范的、不变的,如果每次都要为了处理协议而编写大量代码无疑是冗余的。而在其他如文件IO方面,也需要特定的编码器支持。

因此编解码器出现了,netty提供了可拔插的编解码器构件,能够方便地帮助我们处理各个协议。我们可以这样区分各种编码器:

  • Encoder

    • MessageToByteEncoder //将消息编码为字节
    • MessageToMessageEncoder //将消息编码为消息
  • Decoder

    • ByteToMessageDecoder //将字节解码为消息
    • MessageToMessageDecoder //Decodificar mensaje a mensaje
  • Códec //equivalente a Codificador+Decodificador

La tubería después de agregar el códec es la siguiente

sin título.png

práctica

Un decodificador simple es el siguiente (DatagramPacket es un tipo de contenedor de mensajes definido por netty, que se convierte en el portador de mensajes entrantes como un tipo genérico):

public class LogEventDecoder extends MessageToMessageDecoder<DatagramPacket> {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, DatagramPacket datagramPacket, List<Object> list) throws Exception {
        ByteBuf buf=datagramPacket.content();//获取数据,ByteBuf见第六小节
        //处理数据
        int index=buf.indexOf(0, buf.readableBytes(), LogEvent.SEPARATOR);
        index=buf.indexOf(index+1, buf.readableBytes(), LogEvent.SEPARATOR);
        String filename=buf.slice(0, index).toString(CharsetUtil.UTF_8);
        String logMsg=buf.slice(index+1, buf.readableBytes()-index-1).toString(CharsetUtil.UTF_8);
        LogEvent logEvent=new LogEvent(datagramPacket.sender(),filename, logMsg,System.currentTimeMillis());
        //将解码后消息传递给下一个handler
        list.add(logEvent);
    }
}
复制代码

En la documentación oficial de netty, podemos ver una gran cantidad de implementaciones específicas de códecs que admiten varios protocolos, lo que proporciona un fuerte apoyo para nuestro desarrollo eficiente. Solo algunos de ellos se muestran a continuación:

imagen.png

Por supuesto, también puede escribir su propio códec para analizar protocolos personalizados.

5, Bucle de eventos

EventLoop es el motor de procesamiento central del modelo de subprocesamiento de Netty Reactor, y su flujo de trabajo puede ser el siguiente:

imagen.png

  • Crear EventLoop, que puede pertenecer a un grupo de Event Loop (que puede entenderse como agrupación)
  • El canal lo vincula a un EventLoop cuando se crea
  • Cuando ocurre un evento, se colocará en la cola de eventos.
  • EventLoop toma eventos de la Cola de eventos mediante encuestas y métodos a los correspondientes, y los distribuye a la función de devolución de llamada futura/pipline correspondiente (se devolverá el futuro cuando se ejecute una IP asíncrona, a la que se puede llamar agregando Listner a ella. Consulte la Sección VII )

Para el modelo cliente-servidor, EventLoop funciona de la siguiente manera

imagen.png

  • Cuando comienza el evento, el primer EventLoopGroup se pasará en primer lugar, en el que están vinculados uno o más canales.Este EventLoopGroup distribuirá el evento al EventLoop del canal correspondiente (en el WorkerEventLoop a la derecha)
  • Después de que EventLoop a la derecha complete IO, propagará el evento a lo largo de la tubería
  • Cuando se complete la ejecución futura, EventLoop a la derecha también ejecutará su función de devolución de llamada para él.

6, búfer de bytes

ByteBuf es una API integrada de netty para ayudar a los desarrolladores a procesar bytes. Comparado con el trivial ByteBuffer proporcionado por Java NIO, tiene una experiencia más conveniente y un mejor rendimiento. Sus ventajas incluyen:

  • Fácil de expandir para los usuarios.
  • Copia cero a través de tipos de búfer compuestos incorporados
  • Similar a StringBuffer puede crecer bajo demanda
  • 读写模式间不需要切换,读写使用不同索引
  • 支持链式调用
  • 支持引用计数
  • 支持池化

在这里我并不想对各个API进行解释,这也是没有必要的,详情可以参考官方文档

在这里更希望对ByteBuf的数据结构和使用模式进行展开

数据结构

ByteBuf在数组间维护了两个指针 readerIndex与writerIndex,分别指向了读取和写入的下一个字节的位置,如图:

imagen.png 当然地,在读取内容时需要保障writerIndex大于等于readerIndex,试图越界读取会报出异常。

使用模式

ByteBuffer可以主要分为以下三种使用模式

  • Heap Buffer :将数据存储在JVM的堆空间中

  • Direct Buffer:将数据存储在本地内存中,这样做的好处有

    • 通过本地方法调用分配,除去了中间内存的拷贝,提高IO速度
    • 本地内存不在JVM堆空间中,因此可以降低堆溢出的可能性,并且不需要GC
  • Composite Buffer:将上述二者结合起来,即有存储在堆空间的部分,也有存储在本地内存的部分,使得ByteBuf的使用更加灵活

7、ChannelFuture

Future提供了任务完成时通知引用程序的方式,就如JUC下所定义的一样。

而在netty中,如同ByteBuf之于ByteBuffer,提供了ChannelFuture进行优化。可以通过添加Listener的方式来为future提供回调函数。直接看代码或许能够更好地帮助我们理解,请注意代码中的注释。

//channel进行远程连接,在netty中这是异步的,会立即返回
ChannelFuture future = channel.connect(new InetSocketAddress("192.168.0.1",8888));
//为future注册一个Listener,它重写的方法将会在future执行完毕后被调用
future.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture channelFuture) throws Exception {
        System.out.println("连接成功!");
    }
});
复制代码

8、引导客户端与服务器启动

在netty中,我们通过BootStrap类和ServerBootstrap引导启动。

引导启动事实上是一件相对固定的流程,同样的,使用代码加注释的方式在下方给出。

启动服务器

private void start() throws Exception{
    //创建自定义的channelHandler
    final EchoServerHandler serverHandler=new EchoServerHandler();
    //创建EventLoop组,如果这里指定为OIO便是阻塞式,
    //还可以指定如Epoll,Local,Embeded等方式
    EventLoopGroup group=new NioEventLoopGroup();

    try {
        //创建服务器引导
        ServerBootstrap b=new ServerBootstrap();
        //为ServerBootstrap绑定EventLoop组来处理事件
        b.group(group)
                 //指定是channel的实现类,如果这里指定为OIO便是阻塞式,
                 //还可以指定如Epoll,Local,Embeded等方式
                 //注意这里的传输方式需要和EventLoopGroup一致
                .channel(NioServerSocketChannel.class)
                //服务器需要绑定端口
                .localAddress(new InetSocketAddress(port))
                //添加ChannelHandler来处理网络信息
                //可以直接在里面填入一个ChannelHandler
                //当需要多个ChannelHandler时,需要像下方这样在initChannel方法中添加
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(serverHandler);
                    }
                });
        //绑定到端口上
        ChannelFuture f=b.bind();
        //添加回调函数
        f.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                System.out.println("绑定成功");
            }
        });

    }finally {
        //在退出前要关闭group,通过sync()阻塞直到关闭成功
        group.shutdownGracefully().sync();
    }
}
复制代码

启动客户端

public void start() throws Exception {
    //创建EventLoop组,如果这里指定为OIO便是阻塞式,
    //还可以指定如Epoll,Local,Embeded等方式
    EventLoopGroup group=new NioEventLoopGroup();
    try {
        //创建客户端引导
        Bootstrap b = new Bootstrap();
        //为Bootstrap绑定EventLoop组来处理事件
        b.group(group)
                 //指定是channel的实现类,如果这里指定为OIO便是阻塞式,
                 //还可以指定如Epoll,Local,Embeded等方式
                 //注意这里的传输方式需要和EventLoopGroup一致
                .channel(NioSocketChannel.class)
                //这里填写服务器的socket地址
                .remoteAddress(new InetSocketAddress(host, port))
                //添加ChannelHandler来处理网络信息
                //可以直接在里面填入一个ChannelHandler
                //当需要多个ChannelHandler时,需要像下方这样在initChannel方法中添加
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new EchoClientHandler());
                    }
                });
        //这里当然也可以为future添加listener,只是为了演示如何主动关闭使用了下面的方式
        //通过sync()方法阻塞直到连接成功
        ChannelFuture f = b.connect().sync();
        //通过sync()方法阻塞直到channel关闭成功
        f.channel().closeFuture().sync();
    }finally {
        //在退出前要关闭group,通过sync()阻塞直到关闭成功
        group.shutdownGracefully().sync();
    }
}
复制代码

9、小结

至此,如何使用netty以及大致介绍完毕了,我们不妨再回看文章开头提到的netty优良特性,或许会有新的收获:

  • Soporte asíncrono : solo necesita especificar EventLoopGroup y el canal como asíncrono al inicio, y netty los encapsula
  • Impulsado por eventos : el proceso de procesamiento de información de red de Netty es esencialmente el procesamiento paso a paso en ChannelHandler cuando la información de red entra y sale
  • Desarrollo rápido : como marco, netty en sí tiene la función de código estándar para facilitar el desarrollo, y proporciona un ChannelHandler estándar, solo necesitamos reescribir el método; también proporciona una gran cantidad de códecs, para que podamos conectar y jugar Agregar análisis de protocolo de
  • Alto rendimiento : comunicación asíncrona de Netty, tecnología de agrupación y copia cero ByteBuf, modelo basado en eventos de Reactor, futuro asíncrono y devolución de llamada, soporte para análisis de protocolo personalizado, diseño de serialización sin bloqueos en canalización, una gran cantidad de optimizaciones de concurrencia interna como CAS, etc. Proporciona un rendimiento potente para netty.

10. Otros

  • Este artículo solo presenta brevemente el uso y el diseño de netty. Para obtener un análisis detallado, consulte la [Práctica de Netty] de Norman Maurer, y también adjunte el código provisto en el libro , principalmente consulte el Capítulo 2, Capítulo 12, Capítulo 13 Provisto Caso práctico
  • La versión netty utilizada en este artículo es 4.1.12.Final y las dependencias de maven son las siguientes:
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.12.Final</version>
</dependency>
复制代码

Referirse a:

Supongo que te gusta

Origin juejin.im/post/7150198854237814792
Recomendado
Clasificación