Netty 소스코드 읽기(2) - 서버 소스코드 요약

이상에서는 클라이언트의 소스코드에 대한 전반적인 이해를 하여 서버의 소스코드를 이해하기 쉽게 서버와 클라이언트의 차이점을 분석해보도록 하겠습니다.

목차

차이점

 ④

 ①


차이점

 ④

클라이언트: .option(ChannelOption.TCP_NODELAY, 참)

TCP/IP 프로토콜에서는 아무리 많은 데이터를 보내더라도 항상 프로토콜 헤더가 데이터 앞에 추가되며 동시에 상대방이 데이터를 수신하면 ACK를 보내 확인을 표시해야 합니다. 네트워크 대역폭을 최대한 활용하기 위해 TCP는 항상 가능한 한 많은 데이터를 보내려고 합니다. 여기에는 Nagle이라는 알고리즘이 포함되며, 그 목적은 많은 작은 데이터 블록으로 네트워크가 플러딩되는 것을 방지하기 위해 가능한 한 큰 데이터 블록을 보내는 것입니다.
TCP_NODELAY는 Nagle 알고리즘을 활성화하거나 관련하는 데 사용됩니다. 높은 실시간 성능이 요구되고 데이터가 즉시 전송되는 경우 이 옵션을 true로 설정하여 Nagle 알고리즘을 비활성화하고 전송 횟수를 줄이고 네트워크 상호 작용을 줄이려면 false로 설정하고 일정 크기를 기다립니다. 보내기 전에 누적됩니다. 기본값은 거짓입니다.

서버: .option(ChannelOption.SO_BACKLOG, 100)

서버측 TCP 커널 모듈은 우리가 A와 B라고 부르는 두 개의 대기열을 유지합니다. 클라이언트가 서버에 연결되면 TCP 커널 모듈은 두 번째 핸드셰이크 동안 클라이언트 연결을 A 대기열에 추가하고 세 번째 핸드셰이크 동안 핸드셰이크에서 TCP는 클라이언트 연결을 A 대기열에서 B 대기열로 이동합니다. 연결이 완료된 후 애플리케이션의 수락이 반환되고 수락은 B 대기열에서 3방향 핸드셰이크를 완료한 연결을 제거합니다.

A queue와 B queue의 길이의 합이 backlog인데, A queue와 B queue의 길이의 합이 backlog보다 크면 TCP 커널에 의해 새로운 연결이 거부됩니다. 백로그가 너무 작아 수락 속도를 유지할 수 없습니다.AB 대기열이 가득 차서 새 클라이언트가 연결할 수 없습니다.백로그는 프로그램에서 지원하는 연결 수에 영향을 미치지 않습니다. . 백로그는 수락에 의해 제거되지 않은 연결에만 영향을 미칩니다.

 또한 일반적으로 사용됨: .option(ChannelOption.SO_REUSEADDR, true)

이 매개변수는 로컬 주소와 포트를 재사용할 수 있음을 나타냅니다. 예를 들어 서버 프로세스가 수신을 위해 TCP 포트 80을 점유하는 경우 포트가 다시 수신되면 오류가 반환됩니다. 이 매개변수를 사용하면 문제를 해결할 수 있습니다. 이 매개 변수는
서버 프로그램에서 더 일반적으로 사용되는 포트를 공유할 수 있도록 합니다.

클라이언트: .channel(NioSocketChannel.class)

서버: .channel(NioServerSocketChannel.class)

로드 방법과 유형 결정 프로세스는 동일하므로 인스턴스화의 차이점에 중점을 두겠습니다.

NioSocketChannel과 유사하게 newSocket 메서드가 호출되지만 다음 호출이 다릅니다. 서버는 openServerSocketChannel 메서드를 호출하고 클라이언트는 openSocketChannel 메서드를 호출하는데 이름에서 알 수 있듯이 하나는 클라이언트 측의 Java SocketChannel이고 다른 하나는 서버 측의 Java ServerSocketChannel입니다.

 다음으로 오버로드된 메서드를 호출하고 SelectionKey.OP_ACCEPT를 여기에 전달합니다.Java NIO Socket에 대한 지식이 있는 친구는 Java NIO가 Reactor 모드라는 것을 이해하고 선택기를 사용하여 I/O 다중화를 구현합니다.처음에는 서버는 클라이언트의 연결 요청을 수신해야 하므로 여기에서  SelectionKey.OP_ACCEPT를 설정합니다. 우리는 여전히 고객 인상을 가지고 있습니까? 아래 사진을 비교해보세요

 그런 다음 클라이언트 코드와 마찬가지로 생성자를 단계별로 호출하면 안전하지 않은 파이프라인을 인스턴스화합니다.

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

그러나 newUnsafe는 다릅니다 서버 측의 unsafe 필드는 실제로 AbstractNioMessageChannel#AbstractNioUnsafe의 인스턴스입니다. 그리고 클라이언트는 AbstractNioByteChannel#NioByteUnsafe의 인스턴스입니다.

 ①

클라이언트 측에서는 하나의 EventLoopGroup 객체만 제공했지만 서버 측 초기화에서는 두 개의 EventLoopGroup을 설정했습니다. 하나는 bossGroup이고 다른 하나는 workerGroup입니다. 그렇다면 이 두 EventLoopGroup은 무엇에 사용됩니까? 사실, bossGroup은 서버의 승인, 즉 클라이언트의 연결 요청을 처리하는 데 사용되며 실제로 작업을 수행하는 것은 workerGroup이며 클라이언트 연결 채널의 IO 작업을 담당합니다. 아래 그림과 같이

서버 측의 bossGroup은 클라이언트 연결이 있는지 지속적으로 모니터링하고 새로운 클라이언트 연결이 발견되면 bossGroup은 이 연결에 대한 다양한 리소스를 초기화한 다음 이 클라이언트 연결 Middle에 바인딩할 workerGroup에서 EventLoop를 선택합니다. 서버와 클라이언트 간의 다음 상호 작용 프로세스는 모두 할당된 EventLoop에 있습니다. 소스 코드 분석을 따라가 봅시다.

    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup); // 1
        if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        }
        // 2
        this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
        return this;
    }

위의 코드는 두 가지 작업을 수행합니다. 1은 AbstractBootstrap의 그룹 방법을 입력하여 그룹 속성을 지정하는 것입니다. 이는 클라이언트 측에 직접 있습니다. 2는 ServerBootstrap의 childGroup 속성을 지정하는 것입니다. 속성을 초기화한 후 어디에 사용됩니까?

bossGroup : AbstractBootstrap.bind -> AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister는 boosGroup을 NioServerSocketChannsl과 연결합니다.

ChannelFuture regFuture = config().group().register(channel);

 workGroup : 여전히 initAndRegister 메소드에 나타나는 init(channel) 입니다.

   void init(Channel channel) {
        // 只贴出和childGroup有关的关键代码
        final EventLoopGroup currentChildGroup = childGroup;
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

init 메소드는 ServerBootstrap에서 재작성되고 ​​childGroup은 주로 ServerBootstrapAcceptor 클래스로 캡슐화되므로 이 클래스에 중점을 둡니다.

클릭해서 정적 내부 클래스로 판명된 것을 확인했습니다. childGroup과 관련된 작업은 주로 클래스에서 재작성한 channelRead 메서드에 배치됩니다.

 childGroup.register(child).addListener(...);

여기서 자식은 NioSocketChannel이며 이는 workerGroup의 EventLoop가 NioSocketChannel과 연결되어 있음을 의미합니다.

그렇다면 이 channelRead 메서드는 언제 호출됩니까? 클라이언트가 서버에 연결되면 기본 Java NIO ServerSocketChannel은  SelectionKey.OP_ACCEPT  준비 이벤트를 갖고 NioServerSocketChannel.doReadMessages를 호출합니다.

            SocketChannel ch = SocketUtils.accept(javaChannel());
            if (ch != null) {
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }

 accept 메서드를 통해 클라이언트에 대한 연결을 얻은 다음 NioSocketChannel로 캡슐화합니다. 전달된 this는 NioSocketChannel을 캡슐화하는 부모 채널인 NioServerSocketChannel을 참조합니다. 다음으로 Netty의 ChannelPipeline 메커니즘을 통해 read 이벤트가 단계별로 각 핸들러로 전송되므로 앞서 언급한 ServerBootstrapAcceptor.channelRead가 트리거됩니다.

boosGroup 및 workGroup과 마찬가지로 handler 및 childHandler가 여기에 표시되므로 이전 두 개와 동일합니까? 하나는 연결을 처리하고 다른 하나는 IO 이벤트를 처리합니까? 코드를 살펴보겠습니다.

④에서 우리는 ServerBootstrap의 init 메소드 재작성을 언급했습니다. 또한 처리기의 작업도 포함됩니다.

        final ChannelHandler currentChildHandler = childHandler; // 1

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler(); //2
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });

먼저 2(위 코드의 참고 2)를 살펴보십시오. 여기서 config.handler()는 서버 코드 .handler(new LoggingHandler(LogLevel.INFO))에 의해 지정된 처리기입니다. 비어 있지 않으면 파이프라인에 추가합니다. .

 그런 다음 1(위의 코드 주석 1)을 보면 여기서 childHandler는 여전히 ServerBootstrapAcceptor를 구성하는 속성입니다. 이 클래스를 클릭하면 이 childHandler가 ServerBootstrapAcceptor에 의해 재작성된 channelRead 메서드에 있음을 알 수 있습니다(언제 호출되었는지 기억하십니까? 이후에 연결이 설정된 후)

child.pipeline().addLast(childHandler);

이 자식을 기억하세요. 그는 NioSocketChannel이고 childHandler가 그의 파이프라인에 추가됩니다.

요약하면 다음과 같습니다.

  • 처리기는 수락 단계에서 작동하며 클라이언트의 연결 요청을 처리합니다.
  • childHandler는 클라이언트 연결이 설정된 후에 작동하며 클라이언트 연결의 IO 상호 작용을 담당합니다.

추천

출처blog.csdn.net/wai_58934/article/details/127860262