Un démarrage rapide avec le framework réseau hautes performances netty

1. Qu'est-ce que Netty ?

Avant d'utiliser netty, nous devons d'abord savoir quels sont les avantages de netty et quels problèmes il peut résoudre.

Autant citer directement le top content du site officiel de netty :

image.png

La traduction est la suivante : netty est un cadre de programmation réseau piloté par les événements asynchrones qui prend en charge le développement rapide de serveurs et de clients orientés protocole hautes performances et maintenables .

Notez les mots clés en gras ci-dessus :

  • La prise en charge de l' asynchronisme est un support important pour l'efficacité de netty. Dans l'API Java traditionnelle, nous avons besoin d'un codage plus lourd pour utiliser NIO, mais netty encapsule une API facile à utiliser pour nous, et nous pouvons même convertir l'ensemble du service d'OIO en NIO avec une seule ligne de code !
  • Piloté par les événements fait référence à la façon dont netty gère les messages réseau. Le framework nous fournit une interface/classe abstraite complète. Chaque méthode représente divers événements qui se produisent pendant la transmission du réseau. Dans notre processus de codage, nous avons seulement besoin d'améliorer Chaque méthode peut gérer tous types de communication réseau!
  • La vitesse de développement rapide n'est pas seulement due au fait que netty fournit une logique de cadre appropriée pour le traitement de la communication réseau, mais aussi parce qu'il fournit une multitude de codecs matures à l'intérieur. Nous n'avons qu'à les ajouter à notre pipeline pour gérer facilement des choses comme HTTP/HTTPS/Websocket /IMAP/ProtoBuf et autres protocoles
  • Il existe de nombreuses raisons pour des performances élevées sur netty, y compris, mais sans s'y limiter, la prise en charge de NIO, les optimisations zéro copie, multi-threading, etc. Et nous n'avons qu'à utiliser le framework netty pour profiter facilement des performances puissantes que nos prédécesseurs nous ont offertes.

Nous présenterons netty sous les aspects suivants :

  • Flux de travail de netty : obtenez une compréhension générale de la façon dont netty gère les informations du réseau
  • ChannelHandler : un important composant de traitement des informations réseau dans netty
  • Codec : en tant que ChannelHandler spécial, il fournit aux utilisateurs une analyse de protocole pratique
  • EventLoop : Netty distribue en interne des événements basés sur le modèle Reactor
  • ByteBuf : conteneur d'octets efficace et facile à utiliser fourni par netty
  • ChannelFuture : le modèle futur défini par netty lui-même, utilisé lors de l'exécution d'opérations asynchrones
  • Amorçage du démarrage du client et du serveur : la dernière étape, comment démarrer notre service réseau

2. Flux de travail de netty

Pour commencer avec netty, vous pouvez comprendre le flux de travail de netty en une seule image :

canal.png

Ensuite, nous expliquons cette image:

  • Canal : Tout comme ce qui se fait dans JavaNIO, netty résume le fonctionnement des E/S réseau dans un canal, nous n'exploitons donc plus le socket de base, mais le canal encapsulé.
  • pipline : les événements qui circulent du pipeline vers netty doivent être traités via des ChannelHandlers lourds. ChannelHandler est le cadre fourni par netty et les codeurs dépensent la majeure partie de leur énergie. Un pipline est une abstraction qui connecte séquentiellement les ChannelHandlers.
  • ChannelHandler : Comme mentionné ci-dessus, ChannelHandler est le corps principal pour nous de traiter les informations réseau. Le ChannelHandler que nous encodons hérite du ChannelHandler défini par netty, et ses différentes méthodes sont utilisées pour répondre à différents événements, qui est également le principal moteur de netty événements.

3、Gestionnaire de canaux

concept

ChannelHandler est divisé en deux catégories : ChannelInboundHandler et ChannelOutboundHandler. Les événements entrants et les événements sortants dans la figure ci-dessus sont traités respectivement, c'est pourquoi de nombreux auteurs décrivent la relation entre pipline et ChannelHandler comme suit (réseau source d'image) :

image.png

En fait, une telle image est facile à tromper les lecteurs, pensant que ChannelInboundHandler et ChannelOutboundHandler sont sur deux lignes différentes.

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

实践

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

image.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为例,其继承关系如下:

image.png

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

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

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

  • Encoder

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

    • ByteToMessageDecoder //将字节解码为消息
    • MessageToMessageDecoder //Décoder message en message
  • Codec //équivalent à Encoder+Decoder

Le pipeline après l'ajout du codec est le suivant

sans titre.png

pratique

Un décodeur simple est le suivant (DatagramPacket est une sorte de conteneur de messages défini par netty, qui devient le transporteur des messages entrants en tant que type générique) :

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);
    }
}
复制代码

Dans la documentation officielle de netty, nous pouvons voir un grand nombre d'implémentations spécifiques de codecs prenant en charge divers protocoles, ce qui fournit un support solide pour notre développement efficace. Seuls certains d'entre eux sont présentés ci-dessous :

image.png

Bien sûr, vous pouvez également écrire votre propre codec pour analyser les protocoles personnalisés.

5、EventLoop

EventLoop est le moteur de traitement central du modèle de threading Netty Reactor, et son flux de travail peut être le suivant :

image.png

  • Créer EventLoop, qui peut appartenir à un groupe Event Loop (qui peut être compris comme un regroupement)
  • Le canal le lie à un EventLoop lors de sa création
  • Lorsqu'un événement se produit, il sera placé dans la file d'attente des événements
  • EventLoop prend les événements de la file d'attente d'événements par interrogation et méthodes vers ceux correspondants, et les distribue à la fonction de rappel pipline/future correspondante (le futur sera renvoyé lors de l'exécution d'une adresse IP asynchrone, qui peut être appelée en y ajoutant Listner. voir Section VII )

Pour le modèle client-serveur, EventLoop fonctionne comme suit

image.png

  • Lorsque l'événement démarre, le premier EventLoopGroup sera transmis en premier, sur lequel un ou plusieurs canaux sont liés. Cet EventLoopGroup distribuera l'événement à l'EventLoop du canal correspondant (dans le WorkerEventLoop à droite)
  • Une fois que l'EventLoop sur la droite a terminé l'IO, il propagera l'événement le long du pipline
  • Lorsque la future exécution est terminée, l'EventLoop à droite exécutera également sa fonction de rappel pour elle

6、ByteBuf

ByteBuf est une API intégrée de netty pour aider les développeurs à traiter les octets. Comparé au ByteBuffer trivial fourni par Java NIO, il offre une expérience plus pratique et de meilleures performances. Ses avantages incluent :

  • Facile à étendre pour les utilisateurs
  • Zéro copie via les types de tampons composites intégrés
  • Semblable à StringBuffer peut croître à la demande
  • 读写模式间不需要切换,读写使用不同索引
  • 支持链式调用
  • 支持引用计数
  • 支持池化

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

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

数据结构

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

image.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优良特性,或许会有新的收获:

  • Prise en charge asynchrone : il vous suffit de spécifier EventLoopGroup et le canal comme asynchrone au démarrage, et netty les encapsule
  • Axé sur les événements : le processus de traitement des informations réseau de Netty est essentiellement le traitement étape par étape dans ChannelHandler lorsque les informations réseau sont transmises et transmises.
  • Développement rapide : en tant que framework, netty lui-même a la fonction de code standard pour un développement facile, et il fournit un ChannelHandler standard, nous n'avons qu'à réécrire la méthode ; il fournit également un grand nombre de codecs, afin que nous puissions brancher et jouer Ajouter l'analyse de protocole de
  • Haute performance : communication asynchrone de netty, technologie ByteBuf zero-copy et pooling, modèle événementiel Reactor, futur et rappel asynchrones, prise en charge de l'analyse de protocole personnalisée, conception de sérialisation sans verrous dans le pipeline, un grand nombre d'optimisations de concurrence interne telles que CAS, etc. Fournit des performances puissantes pour netty.

10. Autres

  • Cet article ne présente que brièvement l'utilisation et la conception de netty. Pour une analyse détaillée, veuillez vous référer à [Netty Practice] de Norman Maurer, et également joindre le code fourni dans le livre , reportez-vous principalement au chapitre 2, chapitre 12, chapitre 13 fourni Cas pratique
  • La version netty utilisée dans cet article est 4.1.12.Final, et les dépendances maven sont les suivantes :
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.12.Final</version>
</dependency>
复制代码

faire référence à:

Je suppose que tu aimes

Origine juejin.im/post/7150198854237814792
conseillé
Classement