『Javaソース分析』:Java NIOのServer SocketChannel


『Javaソース分析』:Java NIOのServer SocketChannel
前の2つのブログでは,主にソースコードの観点からSelector.open()とselector.select()メソッドの内部実装を大まかに紹介した.
Selectorは、ServerSocketChannel、SocketChannelと連携して使用されるため、ServerSocketChannel、SocketChannelの内部実装を理解する必要があります.このブログでは主にServer SocketChannelを見てみましょう.
ServerSocketChannelクラスについては、主に次の方法でアクセスポイントを追跡します.
1、ServerSocketChannel ssc = ServerSocketChannel.open();
2、ssc.register(selector,SelectionKey.interestOps);
次は1時から
1、Server SocketChannel.open()の解析
ServerSocketChannelクラスのopen()メソッドのソースコードは次のとおりです.
    public static ServerSocketChannel open() throws IOException {
        return SelectorProvider.provider().openServerSocketChannel();
    }

関数機能:ServerSocketChannelを開きます.
を使用して、SelectorProvider.provider().openServerSocketChannel()メソッドの唯一の行を解析します.
Selectorに関するこのブログで分析したように、SelectorProvider()メソッドはwindowsプラットフォームの下でSelectorProviderの実装クラスWindowsSelectorProviderクラスのインスタンスを返します.
WindowsSelectorProvider、SelectorProviderクラスの継承関係は次のとおりです.
  • WindowsSelectorProviderクラスの直接親は、SelectorProviderImpl
  • です.
  • SelectorProviderImplの直接親はSelectorProviderです.

  • したがって、SelectorProvider().openServerSocketChannel()は、windowsSelectorProvider.openServerSocketChannel()と同等です.OpenServerSocketChannel()メソッドは、WindowsSelectorProviderクラスではなく、直接親クラスでSelectorProviderImplクラスで実装されます.
        --------- SelectorProviderImpl --------------
    
        public ServerSocketChannel openServerSocketChannel() throws IOException {
            return new ServerSocketChannelImpl(this);
        }

    これらのコードがSelector.open()とほぼ似ているかどうかを確認します.すなわち、ServerSocketChannel.open()メソッドは、実際にはサブクラスServerSocketChannelImplのオブジェクトインスタンスを生成します.
    次のように、このサブクラスServer SocketChannelImplを見てみましょう.
    ---------  ServerSocketChannelImpl.java --------
    
    
        class ServerSocketChannelImpl
            extends ServerSocketChannel
            implements SelChImpl
    
            
        // Our file descriptor
        private final FileDescriptor fd;
    
        // fd value needed for dev/poll. This value will remain valid
        // even after the value in the file descriptor object has been set to -1
        private int fdVal;
    
        ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
            super(sp);
            this.fd =  Net.serverSocket(true);
            this.fdVal = IOUtil.fdVal(fd);
            this.state = ST_INUSE;
        }

    以上の追跡により、次のような結果が得られます.
    ServerSocketChannel ssc = ServerSocketChannel.open();中sscは実際にそのサブクラス(ServerSocketChannelImpl)オブジェクトを指します.
    説明するのは、ServerSocketChannel ssc=ServerSocketChannel.open()が作成したこの新しいChannelのSocketが最初であり、このSocketに対してbindメソッドで指定したアドレスをバインドしてから接続を受信する必要があります.したがって、次のようなコードは一般的です.
                    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                    //serverSocketChannel      
                    serverSocketChannel.socket().bind(new InetSocketAddress(port));

    ソースコードを追跡する以上、自分でも次の行のコードの背後の論理を理解しました.
            //        
            serverSocketChannel.socket().bind(new InetSocketAddress(9999));

    ServerSocketChannelImplクラスのsocket()メソッドのコードは次のとおりです.
        public ServerSocket socket() {
            synchronized (stateLock) {//      
    
                if (socket == null)
                    socket = ServerSocketAdaptor.create(this);
                return socket;
            }
        }

    このメソッドは、同期によってsocketが単一の例であることを保証するサーバSocketオブジェクトを返します.
    したがって、serverSocketChannel.socket().bind(new InetSocketAddress(9999);ServerSocketChannelのServerSocketを指定したIPアドレスとポートにバインドします.(ここから分かるように、Server SocketChannelには実はServer Socketオブジェクトがあります.ここでは、Server SocketChannelとSocketChannelを使用してサーバ側とクライアントの論理が基本的にServer SocketとSocketと一致している理由が理解できます)
    以上、Server SocketChannel serverSocketChannel=Server SocketChannel.open()について説明します.コードの内部解析.次にchannel.register(//パラメータ...)の内部実装を見てみましょう.
    次に、ServerSocketChannelImplクラスのaccept()メソッドを見てみましょう.コードは次のとおりです.
    解析channel.register(Selector sel,int ops,Object att)
    ソースコードの観点からchannel.register(Selector sel,int ops,Object att)メソッドの内部実装,registerメソッドはServer SocketChannelの親AbstractSelectableChannelで実装される.
    このメソッドのコードは次のとおりです.
        public final SelectionKey register(Selector sel, int ops,
                                           Object att)
            throws ClosedChannelException
        {
            synchronized (regLock) {//      
                if (!isOpen()) //  Channel    
                    throw new ClosedChannelException();
                if ((ops & ~validOps()) != 0) //        
                    throw new IllegalArgumentException();
                if (blocking) //
                    throw new IllegalBlockingModeException();
                SelectionKey k = findKey(sel); //         Selector  SelectionKey
                /*
                      k  null,         Selector     ,       ops   SelectionKey   。
                      k null,          Selector   ,        ,    SelectionKey。
                */
                if (k != null) {
                    k.interestOps(ops);
                    k.attach(att);
                }
                if (k == null) {
                    // New registration
                    synchronized (keyLock) {
                        if (!isOpen())
                            throw new ClosedChannelException();
                        k = ((AbstractSelector)sel).register(this, ops, att);
                        addKey(k);
                    }
                }
                return k;
            }
        }
        //    :                 。  ServerSocketChannel    "    ",   SelectionKey.OP_ACCEPT。
        public final int validOps() {
            return SelectionKey.OP_ACCEPT;
        }
    ---------- AbstractSelectableChannel  ----------
        //    :             Selector SelectionKey
        private SelectionKey findKey(Selector sel) {
            synchronized (keyLock) {
                if (keys == null)
                    return null;
                for (int i = 0; i < keys.length; i++)
                    if ((keys[i] != null) && (keys[i].selector() == sel))
                        return keys[i];
                return null;
            }
        }
    

    channel.register(Selector sel,int ops,Object att)関数機能:このチャネルchannelを指定したselectorに登録し、SelectionKeyオブジェクトインスタンスを返します.
    registerこの方法の実装コード上の論理には以下の4つの点がある.
    1、まずチャンネルチャンネルが開いているかどうかをチェックし、開いていない場合は異常を投げ、開いている場合は2を行う.
    2、指定したinterestコレクションが有効かどうかをチェックします.効果がなければ、異常を投げます.そうでなければ3を行います.ここで特に強調したいのは、ServerSocketChannelでは新しい接続のみがサポートされているため、interestコレクションopsはops&~sectionKey.OP_ACCEPT!=0を満たしています.つまり、SelectorにサーバSocketChannelが登録されたときのイベントにはSelectionKey.OP_しか含まれません.ACCEPT.
    3、通路に対してブロックモードの検査を行い、ブロックモードでない場合は異常を投げ、そうでない場合は4を行う.
    3つ目に興味深いものを見つけました.次のブログ(http://ifeve.com/selectors/)でSelectorを紹介するときに、Selectorと一緒に使用する場合、Channelは非ブロックモードでなければなりません.これは、FileChannelを非ブロッキングモードに切り替えることができないため、FileChannelをSelectorとともに使用できないことを意味します.ソケットチャネルはすべて可能です.
    当時、このブログを見たときはまだよく理解していませんでしたが、register()メソッドの内部ソースコードにこのようなチェックがあるのを見て、すぐに分かりました.
            if (blocking) //
                throw new IllegalBlockingModeException();
    

    コードを書くとき、channel.configureBlocking(false)という行のコードをよく知っています.すなわちselectorと使用する前に、チャネルを非ブロックに設定します.
        ---------  AbstractSelectableChannel    configureBlocking()       :
        /
        *
                :         
                               ,    implConfigureBlocking         。
                            。
        *
        /
        public final SelectableChannel configureBlocking(boolean block)
            throws IOException
        {
            synchronized (regLock) {
                if (!isOpen())
                    throw new ClosedChannelException();
                if (blocking == block)
                    return this;
                if (block && haveValidKeys())
                    throw new IllegalBlockingModeException();
                implConfigureBlocking(block);
                blocking = block;
            }
            return this;
        }

    AbstraactSelectableChannelクラスには、このチャネルが閉塞性であるか非閉塞性であるかを表すbooleanタイプ変数があります.デフォルトはtrueです.関数の役割は、AbstractSelectableChannelクラスのblocking変数をfalseに設定することです.
    4、指定したSelector上のカレントチャネルのSelectionKeyを取得し、結果をkで表すとする.以下、kがnullであるか否かについて異なる処理を行う.kがnullでない場合、このチャネルchannelがSelectorに登録されていることを示す場合は、指定したopsをSelectionKeyに直接追加すればよい.kがnullの場合、このチャネルがSelectorに登録されていないことを示す場合は、まず登録を行い、対応するSelectionKeyに所定の値opsを設定する必要があります.
    上記の4点はregisterメソッド内部実装プロセスの概略論理である.ここでは、Server SocketChannelのregisterメソッドのこの行のコードについて説明する必要があります.
    k = ((AbstractSelector)sel).register(this, ops, att);
    

    この行のコードのselは、実際にはWindowsSelectorImplオブジェクトです.次に、このクラスのWindowsSelectorImplクラスのregisterメソッド(registerメソッドがSelectorImplクラスで実装されている)を見てみましょう.
        protected final SelectionKey register(AbstractSelectableChannel ch,
                                              int ops,
                                              Object attachment)
        {
            if (!(ch instanceof SelChImpl)) //       
                throw new IllegalSelectorException();
            SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
            k.attach(attachment);
            synchronized (publicKeys) {
                implRegister(k);
            }
            k.interestOps(ops);
            return k;
        }
    

    この方法はSelectorがServerSocketChannelと連絡を取るポイントです.次の方法のコードを分析します.
    1、SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);行のコードは、Server SocketChannelオブジェクトとSelectorオブジェクトのインスタンスに基づいてSelectionKeyオブジェクトを構築する機能です.
    SelectionKeyImplクラスを見ることができます.
        final SelChImpl channel;                            // package-private
        final SelectorImpl selector;                        // package-private
    
        // Index for a pollfd array in Selector that this key is registered with
        private int index;
    
        private volatile int interestOps;
        private int readyOps;
    
        SelectionKeyImpl(SelChImpl ch, SelectorImpl sel) {
            channel = ch;
            selector = sel;
        }

    したがって、SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);は、この行のコードによって、SelectionKeyオブジェクトのインスタンスを得る.
    2、k.attach(attachment);行のコードの役割は、SelectionKeyオブジェクトのattachmentを指定値に設定することです.
    SelectionKeyクラスのattachメソッドコードは次のとおりです.
        --------SelectionKey  -----------
        public final Object attach(Object ob) {
            return attachmentUpdater.getAndSet(this, ob);
        }
        private static final AtomicReferenceFieldUpdater
            attachmentUpdater = AtomicReferenceFieldUpdater.newUpdater(
                SelectionKey.class, Object.class, "attachment"
            );
    
    

    3、implRegister(k);、これはWindowsSelectorImplクラスの1つの方法です.メソッドの具体的なコードは次のとおりです.
    
    
        private SelectionKeyImpl[] channelArray = new SelectionKeyImpl[INIT_CAP];// INIT_CAP = 8
    
        protected void implRegister(SelectionKeyImpl ski) { 
            //     ,     Selector       ,          register       ,         。
            synchronized (closeLock) {
                if (pollWrapper == null)
                    throw new ClosedSelectorException();
                growIfNeeded();//       
                //    Channel SelectionKey   Selector ,       totalChannels   SelectionKey 。
                channelArray[totalChannels] = ski;
                ski.setIndex(totalChannels);
    
                fdMap.put(ski);
                keys.add(ski);  // ski   Selector  Set  
                pollWrapper.addEntry(totalChannels, ski);//  : ski                  
                totalChannels++;
            }
        }

    上記のメソッドのpollWrapper.addEntry(totalChannels, ski);行のコードは、Selectorに登録されるSelectionKey(サーバSocketChannelを表す)に保存されるキーです.
    PollArrayWrapperクラスのaddEntryメソッドも見てみましょう.
    PollArrayWrapperクラス
        // Prepare another pollfd struct for use.
        void addEntry(int index, SelectionKeyImpl ski) {
            putDescriptor(index, ski.channel.getFDVal());
        }
    
        // Access methods for fd structures
        void putDescriptor(int i, int fd) {
            pollArray.putInt(SIZE_POLLFD * i + FD_OFFSET, fd);
        }

    オブジェクトpollArrayはAllocatedNativeObjectオブジェクトで、PollArrayWrapperクラスのコンストラクション関数で初期化されています.PollArrayWrapperクラスの2つの定数は次のとおりです.
        private static final short FD_OFFSET     = 0; // fd offset in pollfd
        private static final short EVENT_OFFSET  = 4; // events offset in pollfd

    AllocatedNativeObjectクラスのputIntメソッド(親NativeObject)のコードは次のとおりです.
       final void putInt(int offset, int value) {
            unsafe.putInt(offset + address, value);
        }

    putInt(int offset,int value)関数機能:指定した物理アドレスにファイル識別子を直接書き込む.
    以上、((AbstractSelector)sel).register(this,ops,att);のregisterメソッドの詳細な分析.channel.register(selector,ops)の実装ロジック全体を分析した.
    以上から、channel.register(selector,ops)文を使用して指定したselectorにchannelを登録すると、実際にはpollWrapperに保存され、selector.select()メソッドで実現される論理は、このpollWrapperを遍歴して、そのチャネルが準備されていることを見ることです.
    小結
    Selector.open()、selector.select()およびchannel.register(selector,ops)を解析した後、Java NIOのメカニズムの原理は少し明確になった.しかし、理解を推測し続ける必要がある.
    あとでSelectionKeyについても紹介します.