netty 귀속 포트 시작 서비스 원본 분석

14577 단어 Netty

netty 귀속 포트 시작 서비스 원본 분석


이 편은 넷티가 포트를 어떻게 연결하고 서비스를 시작하는지 다루고 있다.서비스를 시작하는 과정에서 넷티 각 핵심 구성 요소가 어떻게 넷티의 핵심을 구성하는지 알게 될 것이다
간단한 서버 시작 코드
아래의 코드에 중점을 두다
b.bind(8888).sync();

뒤따라 들어가다
public ChannelFuture bind(int inetPort) {
    return bind(new InetSocketAddress(inetPort));
} 

포트 번호로 InetSocketAddress를 만들고 계속 따라가세요
public ChannelFuture bind(SocketAddress localAddress) {
    validate();
    if (localAddress == null) {
        throw new NullPointerException("localAddress");
    }
    return doBind(localAddress);
}

validate () 를 통해 서비스 시작에 필요한 인자를 검증한 다음 DoBind () 를 호출하고 DoBind () 로 들어갑니다.
private ChannelFuture doBind(final SocketAddress localAddress) {
    //...
    final ChannelFuture regFuture = initAndRegister();
    //...
    final Channel channel = regFuture.channel();
    //...
    doBind0(regFuture, channel, localAddress, promise);
    //...
    return promise;
}

우리는 위의 두 가지 방법, initAndRegister(), 그리고dobind0()에 중점을 두었다.

initAndRegister()

final ChannelFuture initAndRegister() {
    Channel channel = null;
    // ...
    channel = channelFactory.newChannel();
    //...
    init(channel);
    //...
    ChannelFuture regFuture = config().group().register(channel);
    //...
    return regFuture;
}

initAnd Register에서 세 가지 일을 했습니다.new 채널 2.이 채널이 채널을 대상에 등록하기
우리 는 점차 이 세 가지 일 을 분석한다

1. new 하나의 채널


여기 있는 채널은 서비스가 시작될 때 만들어졌기 때문에 일반 Socket 프로그래밍의 ServerSocket과 대응하여 서버가 귀속될 때 지나가는 흐름을 나타낼 수 있습니다
우리는 이 채널이 채널 팩토리 new를 통해 나온 것을 발견했다. 이 채널은 최종적으로 Reflective Channel 팩토리로 호출되었다.newChannel() 메서드
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
    @Override
    public T newChannel() {
        try {
            return clazz.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + clazz, t);
        }
    }
}

반사하는 방식으로 대상을 만듭니다. 이class는 바로 우리가 ServerBootstrap에 전송한NioServerSocketChannel입니다.class
이제 NioServerSocketChannel의 기본 구조 함수에 중심을 둘 수 있습니다.
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    //...
    return provider.openServerSocketChannel();
}

SelectorProvider를 통해openServerSocketChannel () 서버 사이드 채널을 만들고 다음 방법으로 들어갑니다
public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

하나의 서버 채널이 생성되었습니다. 이 세부 사항을 연결할 때, 우리는netty의 몇 가지 기본 구성 요소를 추출합니다. 먼저 다음과 같이 요약합니다.
  • Channel
  • ChannelConfig
  • ChannelId
  • Unsafe
  • Pipeline
  • ChannelHander

  • 요약하자면, 사용자 호출 방법Bootstrap.bind(port)의 첫 번째 단계는 반사 방식으로 new의 Nio Server Socket Channel 대상을 반사하고 new의 과정에서 일련의 핵심 구성 요소를 만드는 것이다.

    2. init 이 channel

    @Override
    void init(Channel channel) throws Exception {
    
    
        /**
        1.  option attr,   options0()  attrs0(),      options attrs   channelConfig  channel 
         */
        final Map, Object> options = options0();
            synchronized (options) {
                channel.config().setOptions(options);
         }
        final Map, Object> attrs = attrs0();
        synchronized (attrs) {
            for (Entry, Object> e: attrs.entrySet()) {
                @SuppressWarnings("unchecked")
                AttributeKey key = (AttributeKey) e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }
    
        ChannelPipeline p = channel.pipeline();
    
        /**
        2.     channel option attr
        */
        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry, Object>[] currentChildOptions;
        final Entry, Object>[] currentChildAttrs;
        synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
        }
    
        /**
          3.        
        */
        p.addLast(new ChannelInitializer() {
            @Override
            public void initChannel(Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }
    
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

    3. 이 채널을 어떤 대상에 등록하기


    이 단계에서 우리는 다음과 같은 방법을 분석한다
    ChannelFuture regFuture = config().group().register(channel);
    

    NioEventLoop에서 register로 호출
    @Override
    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise);
        return promise;
    }

    뒤따라 들어가다
    @Override
    public final void register(EventLoop eventLoop, final ChannelPromise promise) {
        // ...
        AbstractChannel.this.eventLoop = eventLoop;
        // ...
        register0(promise);
    }

    EventLoop 이벤트 순환기를 NioServerSocketChannel에 연결한 다음register0 () 을 호출합니다.
    private void register0(ChannelPromise promise) {
        try {
            boolean firstRegistration = neverRegistered;
            doRegister();
            neverRegistered = false;
            registered = true;
    
            pipeline.invokeHandlerAddedIfNeeded();
    
            safeSetSuccess(promise);
            pipeline.fireChannelRegistered();
            if (isActive()) {
                if (firstRegistration) {
                    pipeline.fireChannelActive();
                } else if (config().isAutoRead()) {
                    beginRead();
                }
            }
        } catch (Throwable t) {
            closeForcibly();
            closeFuture.setClosed();
            safeSetFailure(promise, t);
        }
    }

    doBind0()

    private static void doBind0(
                final ChannelFuture regFuture, final Channel channel,
                final SocketAddress localAddress, final ChannelPromise promise) {
            channel.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    if (regFuture.isSuccess()) {
                        channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                    } else {
                        promise.setFailure(regFuture.cause());
                    }
                }
            });
        }

    우리는 DoBind0(...) 방법을 호출할 때 하나의 Runnable를 포장하여 비동기화하는 것을 발견했다. 비동기화task에 대해 우리는 그것의bind 방법에 들어갔다.
    @Override
    public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
        // ...
        boolean wasActive = isActive();
        // ...
        doBind(localAddress);
    
        if (!wasActive && isActive()) {
            invokeLater(new Runnable() {
                @Override
                public void run() {
                    pipeline.fireChannelActive();
                }
            });
        }
        safeSetSuccess(promise);
    }

    doBind () 방법
    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            //noinspection Since15
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }

    최종적으로 jdk에 있는bind 방법으로 바뀌었습니다. 이 줄 코드가 지나간 후에 정상적인 상황에서 포트의 귀속을 진행했습니다.

    총결산


    netty 서비스 시작 과정
  • 시작 클래스 파라미터를 설정하는데 가장 중요한 것은 채널
  • 을 설정하는 것이다.
  • 서버에 대응하는 채널을 만들고 채널Config,채널Id,채널Pipeline,채널Handler,Unsafe 등 다양한 구성 요소를 생성합니다
  • 서버에 대응하는 채널을 초기화하고attr,option,그리고 서브채널의attr,option을 설정하며 서버의 채널에 새 채널 접속기를 추가하고addHandler,register 등 이벤트
  • 를 출발합니다.
  • jdk 밑에 호출하여 포트 귀속을 하고 active 이벤트를 터치합니다. active가 터치할 때 실제 서비스 포트 귀속을 합니다
  • 좋은 웹페이지 즐겨찾기