Netty 핵심 소스코드 분석 (3) 비즈니스 요청 실행의 핵심 - ChannelPipeline, ChannelHandler, ChannelHandlerContext 소스코드 분석

시리즈 기사 디렉토리

Netty 코어 소스코드 분석(1), Netty의 서버측 시작 프로세스 소스코드 분석
Netty 코어 소스코드 분석(2), Netty의 서버측 수신 요청 프로세스 소스코드 분석
Netty 코어 소스코드 분석(3) 비즈니스 요청의 핵심 실행 - ChannelPipeline, ChannelHandler, ChannelHandlerContext 소스코드 분석
Netty 코어 소스코드 분석(4) 하트비트 감지 소스코드 분석
Netty 코어 소스코드 분석(5) 코어 컴포넌트 EventLoop 소스코드 분석

一、ChannelPipeline、ChannelHandler、ChannelHandlerContext

1. 셋의 관계

  1. ServerSocket이 새로운 연결을 생성할 때마다 대상 클라이언트에 해당하는 소켓이 생성됩니다.
    2) 새로 생성된 각 소켓에는 새로운 ChannelPipeline(이하 파이프라인이라고 함)이 할당됩니다.
  2. 각 ChannelPipeline은 여러 개의 ChannelHandlerContext(이하 Context)를 포함하고 4) 함께 이중 연결 목록을 형성하며 이러한 Context는
    addLast 메서드 호출 시 추가되는 ChannelHandler(이하
    핸들러) 를 래핑하는 데 사용됩니다.
    여기에 이미지 설명 삽입
    : ChannelSocket과 ChannelPipeline은 일대일 연관이며 파이프라인 내부의 여러 Context는 연결 목록을 형성하며 Context는 Handler를 캡슐화한 것일 뿐입니다.

요청이 들어오면 Socket에 해당하는 파이프라인으로 들어가 파이프라인의 모든 핸들러를 거치는데, 이는 디자인 모드의 필터 모드입니다.

둘, ChannelPipeline 소스 코드 분석

1. ChannelPipeline 인터페이스 디자인

여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
ChannelPipeline이 ChannelInboundInvoker, ChannelOutboundInvoker 및 Iterable 인터페이스를 상속한다는 것을 알 수 있습니다. 즉, 데이터 아웃바운드 및 인바운드 메서드를 호출할 수 있고 반복 순회를 지원합니다.

ChannelPipeline의 내부 메소드는 기본적으로 핸들러 링크드 리스트의 추가, 삭제, 수정, 확인을 목적으로 한다.

2. ChannelPipeline이 이벤트를 처리합니다.

ChannelPipeline 인터페이스에 그림이 제공됩니다.
여기에 이미지 설명 삽입
이것은 핸들러 목록입니다. 핸들러는 인바운드 이벤트 및 아웃바운드 이벤트를 처리하거나 가로채는 데 사용되며, 파이프라인은 필터의 고급 형식을 구현하므로 사용자는 이벤트가 처리되고 핸들러가 파이프라인에 있는 방법을 제어할 수 있습니다. 상호 작용하는 방법.
위의 그림은 일반적인 핸들러가 파이프라인에서 IO 이벤트를 처리하는 방법을 설명합니다. IO 이벤트는 inboundHandler 또는 outBoundHandler에 의해 처리되고 ChannelHandlerContext.fireChannelRead 메서드를 호출하여 가장 가까운 핸들러로 전달됩니다.

// 在ChannelInboundHandlerAdapter中的channelRead方法中有着对该方法的默认实现,默认就是调用了ChannelHandlerContext的fireChannelRead方法:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
    ctx.fireChannelRead(msg);
}
// io.netty.channel.AbstractChannelHandlerContext#fireChannelRead
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
    
    
    invokeChannelRead(findContextInbound(), msg);
    return this;
}

그 중 findContextInbound 메소드는 루프를 통해 다음 inboundHandler를 찾습니다.

// io.netty.channel.AbstractChannelHandlerContext#findContextInbound
private AbstractChannelHandlerContext findContextInbound() {
    
    
    AbstractChannelHandlerContext ctx = this;
    do {
    
    
        ctx = ctx.next;
    } while (!ctx.inbound);
    return ctx;
}

invokeChannelRead는 발견된 다음 처리기의 channelRead 메서드를 실행하는 데 사용되며 이는 실행기 체인과 동일합니다.

// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    
    
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
    
    
        next.invokeChannelRead(m);
    } else {
    
    
        executor.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                next.invokeChannelRead(m);
            }
        });
    }
}
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
    
    
    if (invokeHandler()) {
    
    
        try {
    
    
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
    
    
            notifyHandlerException(t);
        }
    } else {
    
    
        fireChannelRead(msg);
    }
}

끝에서 앞으로 이동한다는 점을 제외하면 outBound도 마찬가지입니다.

그리고 이러한 핸들러는 파이프라인에 저장됩니다.

여기에 이미지 설명 삽입
일반적으로 파이프라인에는 여러 핸들러가 있습니다.예를 들어 일반적인 서버에는 각 채널의 파이프라인에 코덱 + 비즈니스 핸들러와 같은 핸들러가 있습니다.

비즈니스 프로그램은 스레드를 차단할 수 없으며 이는 IO 속도에 영향을 미치고 전체 Netty 프로그램의 성능에 영향을 미칩니다. 비즈니스 프로그램이 빠르면 IO 스레드에 배치할 수 있고 그렇지 않으면 비동기적으로 실행해야 합니다(MQ 사용). 또는 처리기를 추가할 때 스레드 풀을 추가합니다. 예를 들면 다음과 같습니다.

// 下面这个任务执行的时候,将不会阻塞IO线程,执行的线程来自group线程池
pipeline.addLast(group, "handler", new MyHandler());

셋, ChannelHandler 소스 코드 분석

1. 채널 핸들러 인터페이스

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;


    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
    
    
        // no value
    }
}

Netty의 @Sharable 주석은 ChannelHandler가 여러 ChannelPipeline 간에 공유될 수 있는지 여부, 즉 ChannelHandler가 상태 비저장인지 여부를 식별하는 데 사용됩니다. @Sharable 주석으로 표시된 ChannelHandler는 여러 ChannelPipeline 간에 공유할 수 있으므로 리소스 소비를 줄일 수 있습니다. Netty에서는 ChannelHandler의 생성자가 호출되거나 ChannelPipeline에 추가될 때마다 새 인스턴스가 생성됩니다. 따라서 stateful ChannelHandler의 경우 @Sharable로 표시할 수 없습니다.

ChannelHandler를 재사용할 때 스레드 안전 문제가 고려된다는 점에 유의해야 합니다. @Sharable 주석이 달린 ChannelHandler 클래스의 경우 여러 ChannelPipeline 간에 공유할 때 스레드 안전성이 보장되어야 합니다. ChannelHandler 클래스에 스레드로부터 안전하지 않은 멤버 변수가 포함되어 있으면 스레드 안전 문제를 방지하기 위해 멤버 변수에서 동기화해야 합니다. 따라서 이 또한 @Sharable 어노테이션을 사용할 때 주의 깊게 고려해야 할 사항이다.

ChannelHandler의 역할은 IO 이벤트를 처리하거나 IO 이벤트를 가로채 다음 처리기 ChannelHandler로 전달하는 것입니다. Handler는 이벤트를 처리할 때 inbound와 outbound로 구분되어 양방향의 동작이 다르기 때문에 Netty는 ChannelHandler를 상속하기 위해 ChannelInboundHandler와 ChannelOutboundHandler라는 두 개의 하위 인터페이스를 정의한다.

2. ChannelInboundHandler 인바운드 인터페이스

public interface ChannelInboundHandler extends ChannelHandler {
    
    

    // channel注册时调用
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;

    // channel取消注册时调用
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

    // channel处于活动状态时调用
    void channelActive(ChannelHandlerContext ctx) throws Exception;

    // channel非活动状态时调用
    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    // 从channel读取数据时被调用
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    // 数据读取完成时被调用
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    // 用户事件触发
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

    // 通道可写状态被触发时调用
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;

    // 发生异常时调用
    @Override
    @SuppressWarnings("deprecation")
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}

우리는 필요한 특정 이벤트에 주의를 기울이고 재작성된 메서드에서 자체 논리를 구현하기 위해 이러한 메서드 중 일부를 다시 작성해야 합니다. Netty 이벤트가 발생하면 Netty는 해당 재작성 메서드를 다시 호출합니다.

3. ChannelOutboundHandler 아웃바운드 인터페이스

public interface ChannelOutboundHandler extends ChannelHandler {
    
    
    // 当请求将channel绑定到本地端口时调用
    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;

    // 发生连接时调用
    void connect(
            ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception;

    // 断开连接时调用
    void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    // 当请求关闭channel时调用
    void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    // 取消注册时调用
    void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    
    void read(ChannelHandlerContext ctx) throws Exception;

    // 在进行写操作时调用。写操作将通过ChannelPipeline写入消息。一旦调用Channel.flush(),它们就准备好被刷新到实际的Channel
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;

    // 在执行刷新操作时调用。flush操作将尝试flush所有以前写的挂起的消息
    void flush(ChannelHandlerContext ctx) throws Exception;
}

아웃바운드 작업은 데이터에 연결하거나 데이터를 쓰는 방법입니다.

4. ChannelDuplexHandler는 아웃바운드 및 인바운드 이벤트를 처리합니다.

ChannelDuplexHandler는 ChannelInboundHandlerAdapter 클래스를 상속하고 ChannelOutboundHandler 인터페이스를 구현하며 모든 인바운드 및 아웃바운드 이벤트를 처리할 수 있습니다.

이벤트를 처리하기 위해 ChannelDuplexHandler를 사용하지 않으려고 합니다. 인바운드 및 아웃바운드 이벤트는 독립적인 클래스에서 처리하는 것이 가장 좋으며 그렇지 않으면 혼동하기 쉽습니다.

넷째, ChannelHandlerContext 소스 코드 분석

1. ChannelHandlerContext 인터페이스

public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker

여기에 이미지 설명 삽입
ChannelHandlerContext 인터페이스는 AttributeMap, ChannelInboundInvoker 및 ChannelOutboundInvoker 인터페이스를 상속합니다.
여기에 이미지 설명 삽입
ChannelHandlerContext는 ChannelInboundInvoker 및 ChannelOutboundInvoker 인터페이스의 메서드를 상속할 뿐만 아니라 자체 메서드 중 일부를 정의합니다. 이러한 메서드는 해당 채널, 실행기, 처리기, 파이프라인, 메모리 할당자 및 컨텍스트 컨텍스트의 연결된 처리기 삭제 여부를 가져올 수 있습니다. .

컨텍스트는 파이프라인에서 핸들러를 컨텍스트가 쉽게 작동할 수 있도록 핸들러와 관련된 모든 클래스를 래핑하는 것입니다.

2. ChannelInboundInvoker 및 ChannelOutboundInvoker 인터페이스

여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
이 두 호출자는 인바운드 또는 아웃바운드 메소드를 위한 것으로, 인바운드 또는 아웃바운드 핸들러의 외부 계층에 있는 계층을 래핑하여 메소드 전후에 일부 특정 작업을 가로채고 수행하는 목적을 달성합니다.

5. 생성 과정의 소스코드 분석

1. SocketChannel 생성 프로세스는 파이프라인을 생성합니다.

이전 기사에서 우리는 doReadMessages 메소드가 NioSocketChannel을 생성할 것이라고 분석했습니다:
Netty 코어 소스 코드 분석(2), Netty 서버 수신 요청 프로세스의 소스 코드 분석

// io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    
    
	// 实际上调用NIO的的accept方法,获取SocketChannel
    SocketChannel ch = SocketUtils.accept(javaChannel());

    try {
    
    
        if (ch != null) {
    
    
        	// 将NIO的SocketChannel包装成NioSocketChannel
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable t) {
    
    
        logger.warn("Failed to create a new channel from an accepted socket.", t);

        try {
    
    
            ch.close();
        } catch (Throwable t2) {
    
    
            logger.warn("Failed to close a socket.", t2);
        }
    }

    return 0;
}

새로운 NioSocketChannel() 과정에서 부모 클래스를 계속 찾으면 AbstractChannel의 구성 방법을 찾을 수 있습니다.

protected AbstractChannel(Channel parent) {
    
    
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

protected DefaultChannelPipeline newChannelPipeline() {
    
    
    return new DefaultChannelPipeline(this);
}

이때 DefaultChannelPipeline이 생성되고 DefaultChannelPipeline의 생성자에서 Pipeline이 초기화됩니다.

protected DefaultChannelPipeline(Channel channel) {
    
    
	// 将channel赋值给channel字段
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    // 创建future和promise,用于异步回调使用
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise =  new VoidChannelPromise(channel, true);
	// 创建inbound的TailContext
    tail = new TailContext(this);
    // 创建outbound的HeadContext(实际上实现了inbound和outbound两者)
    head = new HeadContext(this);
	// 形成双向链表
    head.next = tail;
    tail.prev = head;
}

2. 파이프라인의 addLast 메소드

서버 측에서 파이프라인의 addLiast 메서드를 통해 핸들러를 추가할 때의 소스 코드:

// io.netty.channel.DefaultChannelPipeline#addLast(io.netty.channel.ChannelHandler...)
@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
    
    
    return addLast(null, handlers);
}
// io.netty.channel.DefaultChannelPipeline#addLast(io.netty.util.concurrent.EventExecutorGroup, io.netty.channel.ChannelHandler...)
@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;
}
// io.netty.channel.DefaultChannelPipeline#addLast(io.netty.util.concurrent.EventExecutorGroup, java.lang.String, io.netty.channel.ChannelHandler)
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    
    
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
    
     // 多线程安全问题
    	// 检查这个handler实例是否是共享的,如果不是,并且已经被别的pipeline使用了,则抛出异常
        checkMultiplicity(handler);
		// 创建一个Context,我们可以看出,每添加一个Handler都会关联一个Context
        newCtx = newContext(group, filterName(name, handler), handler);
		// 将Context追加到链表中
        addLast0(newCtx);

        // If the registered is false it means that the channel was not registered on an eventloop yet.
        // In this case we add the context to the pipeline and add a task that will call
        // ChannelHandler.handlerAdded(...) once the channel is registered.
        /*
			如果这个通道还没有注册到selector上,就将这个Context添加到这个pipeline的待办任务中。
			当注册好了以后,就会调用callHandlerAdded0方法(默认是什么都不做,用户可以实现这个方法)
		*/
        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;
}
// io.netty.channel.DefaultChannelPipeline#addLast0
// 实际上是插在了尾的前一个,而不是尾,因为尾部的Handler用于框架做最后的处理用
private void addLast0(AbstractChannelHandlerContext newCtx) {
    
    
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}

3. 요약

이 시점에서 3개의 객체에 대한 생성 과정은 끝났고, ChannelSocket이 생성될 때마다 바인딩된 파이프라인이 생성되어 일대일 관계가 되며, 파이프라인이 생성되면 테일 노드와 헤드 노드도 생성된다. 초기 연결 목록을 형성합니다.

tail은 인바운드 유형의 핸들러이고 head는 인바운드 및 아웃바운드 유형의 핸들러입니다.

파이프라인의 addLast 메서드가 호출되면 주어진 핸들러에 따라 Context가 생성되고 이 Context가 연결된 목록의 끝(꼬리 앞)에 삽입되고 OK가 됩니다.

컨텍스트는 핸들러를 래핑하고 여러 컨텍스트가 파이프라인에서 이중 연결 목록을 형성합니다.

인바운드 방법은 헤드 노드에서 시작하여 인바운드라고 합니다. 아웃바운드 방법은 테일 노드에서 시작하여 아웃바운드라고 합니다.

여섯, ChannelPipeline 스케줄링 핸들러 소스 코드 분석

요청이 들어오면 ChannelPipeline은 가장 먼저 파이프라인의 관련 메서드를 호출하며, 인바운드 이벤트인 경우 이 메서드는 파이프라인의 흐름을 시작한다는 의미인 fire로 시작합니다. 후속 핸들러가 처리를 계속하도록 합니다.

위에서 분석한 것처럼 ChannelPipeline은 DefaultChannelPipeline을 생성합니다.

또한 DefaultChannelPipeline에는 일련의 이벤트 메서드가 포함되어 있으며 이벤트가 트리거된 후 관련 메서드가 실행된다고 위에서 분석했습니다.

1, 인바운드——fireChannelRead

fireChannelRead 메서드를 예로 들어 소스 코드를 추적해 봅시다.

클라이언트 비즈니스 데이터가 전송되면 DefaultChannelPipeline의 fireChannelRead 메서드를 트리거하여 읽을 데이터가 있음을 나타냅니다.

// io.netty.channel.DefaultChannelPipeline#fireChannelRead
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
    
    
	// 将head传入
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}

// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    
    
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor(); // 获取下一个执行器
    if (executor.inEventLoop()) {
    
    
        next.invokeChannelRead(m); // 执行器调度ChannelRead事件
    } else {
    
    
        executor.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                next.invokeChannelRead(m);
            }
        });
    }
}

이때 handler() 메서드는 현재 ChannelHandler인 this를 반환하고 Handler의 channelRead 메서드를 호출합니다.

//  io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
    
    
    if (invokeHandler()) {
    
    
        try {
    
    
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
    
    
            notifyHandlerException(t);
        }
    } else {
    
    
        fireChannelRead(msg);
    }
}

파이프라인의 첫 번째 핸들러는 시스템에서 생성한 HeadContext이며 이때 HeadContext의 channelRead 메서드가 호출됩니다(메서드는 부모 클래스 DefaultChannelPipeline에서 구현됨).

// io.netty.channel.DefaultChannelPipeline.HeadContext#channelRead
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
    ctx.fireChannelRead(msg);
}
// io.netty.channel.AbstractChannelHandlerContext#fireChannelRead
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
    
    
    invokeChannelRead(findContextInbound(), msg);
    return this;
}
// io.netty.channel.AbstractChannelHandlerContext#findContextInbound
private AbstractChannelHandlerContext findContextInbound() {
    
    
    AbstractChannelHandlerContext ctx = this;
    do {
    
    
        ctx = ctx.next;
    } while (!ctx.inbound);
    return ctx;
}
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    
    
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
    
    
        next.invokeChannelRead(m);
    } else {
    
    
        executor.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                next.invokeChannelRead(m);
            }
        });
    }
}

우리는 AbstractChannelHandlerContext에 fireChannelRead 메서드도 있다는 것을 발견했습니다. 이 메서드는 파이프라인의 다음 핸들러를 찾기 위해 findContextInbound 메서드를 호출한 다음 다음 핸들러의 channelRead 메서드를 호출하는 식입니다.

즉, Handler의 channelRead 메서드에서 ChannelHandlerContext의 fireChannelRead 메서드를 호출하면 메시지가 다음 Handler로 전달됩니다.

2, 아웃바운드——연결

아웃바운드 소스 코드를 분석하기 위해 connect 메서드를 사용합니다.

// io.netty.channel.DefaultChannelPipeline#connect(java.net.SocketAddress, java.net.SocketAddress, io.netty.channel.ChannelPromise)
@Override
public final ChannelFuture connect(
        SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
    
    
        // 从tail开始调用connect事件
    return tail.connect(remoteAddress, localAddress, promise);
}
// io.netty.channel.AbstractChannelHandlerContext#connect(java.net.SocketAddress, java.net.SocketAddress, io.netty.channel.ChannelPromise)
@Override
public ChannelFuture connect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
    
    

    if (remoteAddress == null) {
    
    
        throw new NullPointerException("remoteAddress");
    }
    if (isNotValidPromise(promise, false)) {
    
    
        // cancelled
        return promise;
    }

    final AbstractChannelHandlerContext next = findContextOutbound();
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
    
    
        next.invokeConnect(remoteAddress, localAddress, promise);
    } else {
    
    
        safeExecute(executor, new Runnable() {
    
    
            @Override
            public void run() {
    
    
                next.invokeConnect(remoteAddress, localAddress, promise);
            }
        }, promise, null);
    }
    return promise;
}
// io.netty.channel.AbstractChannelHandlerContext#invokeConnect
private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
    
    
    if (invokeHandler()) {
    
    
        try {
    
    
            ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
        } catch (Throwable t) {
    
    
            notifyOutboundHandlerException(t, promise);
        }
    } else {
    
    
        connect(remoteAddress, localAddress, promise);
    }
}

우리는 ChannelOutboundHandler의 연결 메소드가 인바운드와 정확히 동일한 체인에서 호출된다는 것을 발견했습니다.

3. 요약

여기에 이미지 설명 삽입
컨텍스트는 핸들러를 감싸고 여러 컨텍스트는 파이프라인에서 양방향 연결 목록을 형성합니다.인바운드 방향은 헤드 노드에서 시작하여 인바운드라고 하고 아웃바운드 방법은 테일 노드에서 시작하여 아웃바운드라고 합니다.

노드간 전송은 AbstractChannelHandlerContext 내부의 fire 시리즈 메소드를 통해 이루어지며 현재 노드의 다음 노드를 찾아 연속 전송을 하고 핸들러의 스케줄링이 체인으로 완성된다.

이 시점에서 요청 설정, 요청 수신 및 요청 처리에 이르는 전 과정의 Netty 소스 코드가 완전히 분석되었습니다.
여기에 이미지 설명 삽입

추천

출처blog.csdn.net/A_art_xiang/article/details/130326161