Chapitre 10 Analyse du code source Netty core ②

Pipeline Handler HandlerContext pour créer une analyse de code source
ChannelPipeline scheduling handler handler analyse de code source

Le but de l'analyse du code source
Netty ChannelPipeline, ChannelHandler et ChannelHandlerContext sont des composants très centraux. A partir du code source, nous analysons comment Netty a conçu ces trois composants principaux, et analysons comment ils sont créés et coordonnés.

ChannelPipeline| ChannelHandler| ChannelHandlerContextPrésentation

1.1 La relation entre les trois

  1. Chaque fois que ServerSocket crée une nouvelle connexion, un socket est créé, correspondant au client cible.

  2. Chaque Socket nouvellement créé se verra attribuer un tout nouveau ChannelPipeline (ci-après dénommé pipeline)

  3. Chaque ChannelPipeline contient plusieurs ChannelHandlerContext (ci-après dénommé Context)

  4. Ensemble, ils forment une liste doublement liée. Ces contextes sont utilisés pour encapsuler le ChannelHandler (ci-après dénommé gestionnaire) que nous ajoutons lorsque nous appelons la méthode addLast.
    Insérez la description de l'image ici

  • Dans la figure ci-dessus: ChannelSocket et ChannelPipeline sont des associations un à un, et plusieurs contextes à l'intérieur du pipeline forment une liste liée, et le contexte n'est qu'une encapsulation du gestionnaire.
  • Lorsqu'une requête arrive, elle entre dans le pipeline correspondant au Socket et passe tous les gestionnaires du pipeline. Oui, c'est le mode filtre en mode design.

1.2 Fonction et conception ChannelPipeline

1)
Insérez la description de l'image ici
Une partie du code source de la conception de l' interface du pipeline
Insérez la description de l'image ici

On peut voir que l'interface hérite des interfaces inBound, outBound, Iterable, ce qui signifie qu'il peut appeler la méthode sortante des données et la méthode entrante , et peut également parcourir la liste liée interne . Consultez plusieurs de ses méthodes représentatives. Tous sont destinés aux opérations d'insertion, d'ajout, de suppression et de remplacement de la liste liée du gestionnaire, similaires à une LinkedList. Dans le même temps, il peut également renvoyer le canal (c'est-à-dire, socket)
Insérez la description de l'image ici
1) Dans le document d'interface du pipeline, une image est fournie. Le
flux de données vers le pipeline est poussé sur la pile et le flux sortant du pipeline est poussé hors de la pile.
Insérez la description de l'image ici

Explication de l'image ci-dessus:

  • Il s'agit d'une liste de gestionnaires. Le gestionnaire est utilisé pour traiter ou intercepter les événements entrants et sortants. Le pipeline implémente une forme avancée de filtres afin que les utilisateurs puissent contrôler la façon dont les événements sont traités et comment les gestionnaires interagissent dans le pipeline.

  • La figure ci-dessus décrit la manière typique d'un gestionnaire de gérer les événements d'E / S dans le pipeline. Les événements d'E / S sont gérés par inboundHandler ou outBoundHandler et ChannelHandlerContext.fireChannelReadtransmis au gestionnaire le plus proche en appelant la méthode.
    Insérez la description de l'image ici
    Insérez la description de l'image ici
    Insérez la description de l'image ici

  • Les événements entrants sont gérés par le gestionnaire entrant dans une direction ascendante, comme illustré dans la figure. Le gestionnaire entrant traite généralement les données entrantes générées par le thread d'E / S au bas de la figure. Les données entrantes sont généralement obtenues par exemple à partir de SocketChannel.read (ByteBuffer).

  • Habituellement, un pipeline a plusieurs gestionnaires. Par exemple, un serveur typique aura les gestionnaires suivants dans le pipeline de chaque canal:
    Protocole décoder-convertir les données binaires en objets Java.
    Protocole Java convertit les objets Java en données binaires.
    Les gestionnaires de logique métier exécutent la logique métier réelle (par exemple, l'accès à la base de données)

  • Votre programme d'entreprise ne peut pas bloquer le thread, cela affectera la vitesse des E / S, puis affectera les performances de l'ensemble du programme Netty. Si votre programme d'entreprise est rapide, vous pouvez le mettre dans le thread IO, sinon, vous devez l'exécuter de manière asynchrone. Ou ajoutez un pool de threads lors de l'ajout du gestionnaire,
    par exemple:
    // Lorsque la tâche suivante est exécutée, elle ne bloque pas le thread IO. Le thread exécuté provient du pool de threads de groupe
    pipeline.addLast (group, "handler", new MyBusinessLogicHandler () );
    Ou placez taskQueue ou scheduleTaskQueue

1.3 Fonction et conception de ChannelHandler

  1. Code source
public interface ChannelHandler {

 //当把 ChannelHandler 添加到 pipeline 时被调用
 void handlerAdded(ChannelHandlerContext ctx) throws Exception;
 
//当从 pipeline 中移除时调用
 void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
 
// 当处理过程中在 pipeline 发生异常时调用
@Deprecated
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}

Le rôle de ChannelHandler est de gérer les événements d'E / S ou d'intercepter les événements d'E / S et de les transmettre au gestionnaire de canaux suivant ChannelHandler.
Lorsque le gestionnaire traite des événements, il est divisé en entrants et sortants. Les opérations dans les deux sens sont différentes. Par conséquent, Netty définit deux sous-interfaces pour hériter de ChannelHandler

2) ChannelInboundHandlerInterface d'événement entrant
Insérez la description de l'image ici

  • channelActive est utilisé lorsque Channel est actif;

  • channelRead est appelé lors de la lecture des données de Channel.

  • Les programmeurs doivent réécrire certaines méthodes. Lorsqu'un événement intéressant se produit, nous devons implémenter notre logique métier dans la méthode, car lorsque l'événement se produit, Netty rappellera la méthode correspondante.

3) ChannelOutboundHandlerInterface d'événement sortant
Insérez la description de l'image ici

  • méthode de liaison, appelée lorsque le canal doit être lié à une adresse locale
  • méthode close, appelée lorsque le canal est fermé
  • Les opérations sortantes sont toutes des méthodes similaires de connexion et d'écriture de données.

4) ChannelDuplexHandlerGérer les événements sortants et entrants
Insérez la description de l'image ici

  • ChannelDuplexHandler implémente indirectement l'interface entrante et implémente directement l'interface sortante.
  • Il s'agit d'une classe générale qui peut gérer à la fois les événements entrants et sortants.

1.4 Le rôle et la conception de ChannelHandlerContext

  1. Diagramme UML
    Insérez la description de l'image ici
    ChannelHandlerContext ChannelHandlerContext hérite de l'interface d'appel de méthode sortante et de l'interface d'appel de méthode entrante

1) ChannelOutboundInvokeret une ChannelInboundInvokerpartie de la source
Insérez la description de l'image ici
Insérez la description de l'image ici

  • Ces deux invocateurs sont destinés à la méthode entrante ou sortante, qui consiste à envelopper une couche sur la couche externe du gestionnaire entrant ou sortant pour atteindre l'objectif d'intercepter et d'effectuer certaines opérations spécifiques avant et après la méthode.

2) ChannelHandlerContextUne partie du code source
Insérez la description de l'image ici

  • ChannelHandlerContext hérite non seulement de leurs deux méthodes, mais définit également certaines de ses propres méthodes
  • Ces méthodes peuvent déterminer si le canal, l'exécuteur, le gestionnaire, le pipeline, l'allocateur de mémoire et le gestionnaire associés correspondants dans le contexte contextuel sont supprimés.
  • Le contexte consiste à envelopper tout ce qui concerne le gestionnaire, afin que le contexte puisse facilement faire fonctionner le gestionnaire dans le pipeline

ChannelPipeline| ChannelHandler| ChannelHandlerContextProcessus de création

Il y a 3 étapes pour voir le processus de création:

  • Lorsqu'un ChannelSocket est créé, un pipeline sera créé en même temps.

  • Lorsque l'utilisateur ou le système appelle en interne la méthode add *** du pipeline pour ajouter un gestionnaire, un contexte qui enveloppe ce gestionnaire est créé.

  • Ces contextes forment une liste doublement liée dans le pipeline.

2.1 Socket est créé lorsque Socket est créé; dans le constructeur de AbstractChannel, la classe parent abstraite de SocketChannel

 protected AbstractChannel(Channel parent) {
        this.parent = parent; //断点测试
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline(); 
    }

Déboguer, vous pouvez voir que le code sera exécuté ici, puis continuer à tracer

 protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);

        tail = new TailContext(this);
        head = new HeadContext(this);

        head.next = tail;
        tail.prev = head;
    }

Explication:
1) Attribuez le canal au champ de canal, utilisé pour le canal d'exploitation du pipeline.
2) Créez un avenir et promettez un rappel asynchrone.
3) Créer un rapprochement est tailContextde créer un type à la fois entrant et sortant est de type headContext. Insérez la description de l'image ici
Insérez la description de l'image ici
4) Enfin, deux Contexte reliés entre eux pour former une liste doublement chaînée.
5) tailContext et HeadContext sont très importants, tous les événements du pipeline les traverseront,

2.2 Créer un contexte lors de l'ajout d'un processeur de gestionnaire dans add ** Regardez le contexte créé par la méthode addLast de DefaultChannelPipeline, le code est le suivant

@Override
    public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
        if (handlers == null) { //断点
            throw new NullPointerException("handlers");
        }

        for (ChannelHandler h: handlers) {
            if (h == null) {
                break;
            }
            addLast(executor, null, h);
        }

        return this;
    }

Continuer le débogage

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);

            newCtx = newContext(group, filterName(name, handler), handler);//

            addLast0(newCtx);
            
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

Explication

  1. Ajoutez un gestionnaire au pipeline, le paramètre est le pool de threads, le nom est nul et le gestionnaire est le gestionnaire transmis par nous ou le système. Netty a synchronisé ce code afin d'empêcher plusieurs threads de provoquer des problèmes de sécurité. Les étapes sont les suivantes:
  2. Vérifiez si l'instance de gestionnaire est partagée, sinon, et qu'elle est déjà utilisée par un autre pipeline, puis lève une exception.
  3. Appelez la newContext(group, filterName(name, handler), handler)méthode, créer un contexte . On peut en voir que chaque fois que vous ajoutez un gestionnaire, un contexte associé est créé.
  4. Appelez la méthode addLast pour ajouter le contexte à la liste liée.
  5. Si le canal n'a pas été enregistré avec selecor, ajoutez ce contexte aux tâches en attente de ce pipeline. Une fois l'enregistrement terminé, la méthode callHandlerAdded0 sera appelée (la valeur par défaut est de ne rien faire, l'utilisateur peut implémenter cette méthode).
  6. À ce stade, pour le processus de création de trois objets, vous savez presque la même chose. Comme je l'ai dit initialement, chaque fois qu'un ChannelSocket est créé, un pipeline lié est créé, une relation un-à-un, et un nœud de queue et un nœud de queue sont créés lors de la création du pipeline. Le nœud principal forme la liste chaînée initiale. tail est un gestionnaire de type entrant et entrant, et head est à la fois un gestionnaire de type entrant et sortant. Lorsque la méthode addLast du pipeline est appelée, un contexte est créé en fonction du gestionnaire donné, puis le contexte est inséré à la fin de la liste liée (avant la queue).

Insérez la description de l'image ici

ChannelPipeline L'analyse du code source sur la façon de planifier le gestionnaire

Insérez la description de l'image ici
Insérez la description de l'image ici
Comment DefaultChannelPipeline implémente ces méthodes de tir

1.1 DefaultChannelPipelinecode source

public class DefaultChannelPipeline implements ChannelPipeline {
@Override
    public final ChannelPipeline fireChannelActive() {
        AbstractChannelHandlerContext.invokeChannelActive(head);
        return this;
    }

    @Override
    public final ChannelPipeline fireChannelInactive() {
        AbstractChannelHandlerContext.invokeChannelInactive(head);
        return this;
    }

    @Override
    public final ChannelPipeline fireExceptionCaught(Throwable cause) {
        AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
        return this;
    }

    @Override
    public final ChannelPipeline fireUserEventTriggered(Object event) {
        AbstractChannelHandlerContext.invokeUserEventTriggered(head, event);
        return this;
    }

    @Override
    public final ChannelPipeline fireChannelRead(Object msg) {
        AbstractChannelHandlerContext.invokeChannelRead(head, msg);
        return this;
    }

    @Override
    public final ChannelPipeline fireChannelReadComplete() {
        AbstractChannelHandlerContext.invokeChannelReadComplete(head);
        return this;
    }

    @Override
    public final ChannelPipeline fireChannelWritabilityChanged() {
        AbstractChannelHandlerContext.invokeChannelWritabilityChanged(head);
        return this;
    }
}

Explication: On
peut voir que ces méthodes sont toutes des méthodes entrantes, c'est-à-dire des événements entrants, et la méthode statique est également appelée gestionnaire de tête de type entrant. Ces méthodes statiques appellent la méthode de l'interface ChannelInboundInvoker de la tête, puis appellent la méthode réelle du gestionnaire
Insérez la description de l'image ici
Insérez la description de l'image ici
Insérez la description de l'image ici

1.2 Regardez le code source de l'implémentation de la méthode de tir sortant de piepline

public class DefaultChannelPipeline implements ChannelPipeline {
 @Override
    public final ChannelFuture bind(SocketAddress localAddress) {
        return tail.bind(localAddress);
    }

    @Override
    public final ChannelFuture connect(SocketAddress remoteAddress) {
        return tail.connect(remoteAddress);
    }

    @Override
    public final ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
        return tail.connect(remoteAddress, localAddress);
    }

    @Override
    public final ChannelFuture disconnect() {
        return tail.disconnect();
    }

    @Override
    public final ChannelFuture close() {
        return tail.close();
    }

    @Override
    public final ChannelFuture deregister() {
        return tail.deregister();
    }

    @Override
    public final ChannelPipeline flush() {
        tail.flush();
        return this;
    }

    @Override
    public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
        return tail.bind(localAddress, promise);
    }

    @Override
    public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return tail.connect(remoteAddress, promise);
    }

    @Override
    public final ChannelFuture connect(
            SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        return tail.connect(remoteAddress, localAddress, promise);
    }

    @Override
    public final ChannelFuture disconnect(ChannelPromise promise) {
        return tail.disconnect(promise);
    }
}

Description:

  1. Ce sont toutes des implémentations sortantes, mais le gestionnaire de queue de type sortant est appelé pour le traitement car il s'agit d'événements sortants.
  2. Sortant commence par la queue, et entrant commence par la tête. Parce que le sortant est écrit de l'intérieur vers l'extérieur, à partir de la queue, le gestionnaire précédent peut être traité pour éviter qu'il ne soit manqué, comme le codage. Au contraire, l'entrée est bien sûr entrée de la tête vers l'intérieur, de sorte que les gestionnaires suivants peuvent traiter les données de ces entrées. Tels que le décodage. Ainsi, bien que head implémente également l'interface sortante, il ne démarre pas les tâches sortantes à partir de head
    Insérez la description de l'image ici

2. Sur la façon de planifier, utilisez une image pour représenter:
Insérez la description de l'image ici

Description:

  1. Le pipeline appellera d'abord la méthode statique fireXXX de Context, et passera dans le Context
  2. Ensuite, la méthode statique appelle la méthode invocatrice du contexte, et la méthode invocatrice appelle en interne la vraie méthode XXX du gestionnaire contenu dans le contexte. Une fois l'appel terminé, si vous devez continuer à le renvoyer, appelez la méthode fireXXX2 du contexte, et inversement. Modèle de chaîne de responsabilité

Insérez la description de l'image ici

Publié 138 articles originaux · éloge de won 3 · Vues 7218

Je suppose que tu aimes

Origine blog.csdn.net/weixin_43719015/article/details/105398597
conseillé
Classement