NIOソース読解ノート(二)Channel

14830 ワード

1.チャンネルについて


Channelは、データの読み取りと書き込みを行うオブジェクトです.それをIOの中の流れと見なすことができます.しかし、ストリームとはいくつかの違いがあります.
(1)チャネルは双方向であり、読み書きも可能であるが、ストリームは一方向である(2)チャネルは非同期で読み書きが可能である(3)チャネルの読み書きはbufferオブジェクトによって行わなければならない.上述したように、すべてのデータはBufferオブジェクトによって処理されるので、チャネルにバイトを直接書き込むことはありません.逆に、Bufferにデータを書き込むことです.同様に、チャネルからバイトを読み込むのではなく、チャネルからBufferにデータを読み込み、Bufferからバイトを取得します.
Java NIOでは主に次のようなタイプがあります.
FileChannel:ファイルからデータを読み込むDatagramChannel:UDPネットワークプロトコルデータを読み書きするSocketChannel:TCPネットワークプロトコルデータを読み書きするServerSocketChannel:TCP接続を傍受できる

2.チャンネルに関するいくつかの重要な方法:

  • 2.1チャネルインタフェースにおけるisOpen()およびclose()
  • を定義する
    public interface Channel extends Closeable {
        public boolean isOpen();
        public void close() throws IOException;
    }
    
    

    この2つの方法はすべてのチャネルが実現しなければならない方法であり,ここでは簡単な理解しかしていない.
  • 2.2 open()open()メソッドについては、異なるチャネルで異なる実装がある.ここでもopen()メソッドについて具体的な議論はしません.
  • 2.3チャネルに関するregister
  • Channelの登録定義はSelectableChannelクラスにあります.
    	public abstract SelectionKey register(Selector sel, int ops, Object att)
            throws ClosedChannelException;
        public final SelectionKey register(Selector sel, int ops)
            throws ClosedChannelException
        {
            return register(sel, ops, null);
        }
    

    AbstractSelectableChannelクラスでは、selセレクタopsが設定した関心att生成鍵の添付ファイルの3つのパラメータを入力する必要があります.一般的にnull
    関心定義はSelectionKeyクラスで、次の値があります.
        public static final int OP_READ = 1 << 0;
    
        public static final int OP_WRITE = 1 << 2;
    
        public static final int OP_CONNECT = 1 << 3;
        
        public static final int OP_ACCEPT = 1 << 4;
    
    public final SelectionKey register(Selector sel, int ops,
                                           Object att)
            throws ClosedChannelException
        {
            synchronized (regLock) {
                if (!isOpen())
                    throw new ClosedChannelException();
                if ((ops & ~validOps()) != 0)
                    throw new IllegalArgumentException();
                if (blocking)
                    throw new IllegalBlockingModeException();
                SelectionKey k = findKey(sel);
                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;
            }
        }
    

    まずチャンネルが開いているかどうかを判断し、開かないと異常を放出する.次にチャネルの興味設定が正しいかどうかを判断し、validOps()は異なるチャネルで異なる実現がある.最後に、ブロックモードであるか否かを判断し、ブロックモードである場合、異常を放出する.以上の3つの判断が成立しない場合はfindKeyメソッドに入ります.
    private SelectionKey[] keys = null;
    
    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;
            }
        }
    

    最初の登録であればkeys=null、nullを直接返します.そうでなければ配列を巡回し、現在のSelectionKeyに現在のSelectorがある場合はSelectionKeyに直接戻り、そうでなければnullに戻ります.次に、kがnullであるかどうかを判断します.kがnullでない場合、説明は登録されています.interestOpsメソッドを呼び出します.
    public SelectionKey interestOps(int var1) {
            this.ensureValid();
            return this.nioInterestOps(var1);
        }
    

    まず通過するEnsureValid()は、現在のSelectionKeyが有効かどうかを判断します.
    private void ensureValid() {
            if (!this.isValid()) {
                throw new CancelledKeyException();
            }
        }
    

    isValid()メソッドを呼び出し、無効な場合は例外を放出します.有効な場合はnioInterestOpsメソッドを呼び出します.
    	public SelectionKey nioInterestOps(int var1) {
            if ((var1 & ~this.channel().validOps()) != 0) {
                throw new IllegalArgumentException();
            } else {
                this.channel.translateAndSetInterestOps(var1, this);
                this.interestOps = var1;
                return this;
            }
        }
    

    registerメソッドと同様に,興味値が正しいか否かを判断し,正しくなければ異常を投げ出す.正しい場合はtranslateAndSetInterestOpsメソッドが呼び出され、translateAndSetInterestOpsは異なるチャネルにおいて異なる実装方式を有し、SocketChannelを例にとると、このメソッドの実装はSocketChannelImplクラスにおいて:
    public void translateAndSetInterestOps(int var1, SelectionKeyImpl var2) {
            int var3 = 0;
            if ((var1 & 1) != 0) {
                var3 |= Net.POLLIN;
            }
    
            if ((var1 & 4) != 0) {
                var3 |= Net.POLLOUT;
            }
    
            if ((var1 & 8) != 0) {
                var3 |= Net.POLLCONN;
            }
    
            var2.selector.putEventOps(var2, var3);
        }
    

    各関心値をパラメータvar 3で保存し、次に、WindowsSelectorImplクラスで実装されるselectorのputEventOpsメソッドを呼び出します.
    public void putEventOps(SelectionKeyImpl var1, int var2) {
            Object var3 = this.closeLock;
            synchronized(this.closeLock) {
                if (this.pollWrapper == null) {
                    throw new ClosedSelectorException();
                } else {
                    int var4 = var1.getIndex();
                    if (var4 == -1) {
                        throw new CancelledKeyException();
                    } else {
                        this.pollWrapper.putEventOps(var4, var2);
                    }
                }
            }
        }
    

    まず、ポーリング配列がnullであるかどうかを検出し、nullである場合、異常を放出する.そうでなければvar 4でSelectionKeyImplに保存する配列の下付き文字を保存し、下付き文字に対して正当性検査を行い、最後にポーリング配列に対してイベント応答を設定し、pollWrapper.putEventOpsは最後にnative法である.
    設定が完了したら、nioInterestOpsメソッドに戻り、興味値を保存してSelectionKeyに戻ります.registerメソッドに戻ってattachメソッドを呼び出します.
     	private static final AtomicReferenceFieldUpdater
            attachmentUpdater = AtomicReferenceFieldUpdater.newUpdater(
                SelectionKey.class, Object.class, "attachment"
            );
            
    	 public final Object attach(Object ob) {
            return attachmentUpdater.getAndSet(this, ob);
        }
    

    次にkがnullの場合を見てみましょう.まずチャネルが閉じているかどうかを判断し、閉じていると異常が放出されます.そうしないとAbstractSelectorクラスのregisterメソッドが呼び出されます.このメソッドはSelectorImplクラスで実現されます.
    	protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
            if (!(var1 instanceof SelChImpl)) {
                throw new IllegalSelectorException();
            } else {
                SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
                var4.attach(var3);
                Set var5 = this.publicKeys;
                synchronized(this.publicKeys) {
                    this.implRegister(var4);
                }
    
                var4.interestOps(var2);
                return var4;
            }
        }
    

    まずクラスタイプ検出です.クラスタイプ検出が正しい場合はSelectionKeyImplオブジェクトを作成し、SelectionKeyImplの構築方法に入ります.
     SelectionKeyImpl(SelChImpl var1, SelectorImpl var2) {
            this.channel = var1;
            this.selector = var2;
        }
    

    この構造方法は比較的容易で,channelとselectorオブジェクトに値を付与する.次に、attachメソッドを呼び出して添付ファイルを更新し、次に、WindowsSelectorImplクラスで実装されるimplRegister抽象メソッドを呼び出します.
     protected void implRegister(SelectionKeyImpl var1) {
            Object var2 = this.closeLock;
            synchronized(this.closeLock) {
                if (this.pollWrapper == null) {
                    throw new ClosedSelectorException();
                } else {
                    this.growIfNeeded();
                    this.channelArray[this.totalChannels] = var1;
                    var1.setIndex(this.totalChannels);
                    this.fdMap.put(var1);
                    this.keys.add(var1);
                    this.pollWrapper.addEntry(this.totalChannels, var1);
                    ++this.totalChannels;
                }
            }
        }
    

    まず、growIfNeeded()を拡張する必要があるかどうかを判断します.
     	private static final int MAX_SELECTABLE_FDS = 1024;
    	 private SelectionKeyImpl[] channelArray = new SelectionKeyImpl[8];
    	 private int totalChannels = 1;
      
    	private void growIfNeeded() {
            if (this.channelArray.length == this.totalChannels) {
                int var1 = this.totalChannels * 2;
                SelectionKeyImpl[] var2 = new SelectionKeyImpl[var1];
                System.arraycopy(this.channelArray, 1, var2, 1, this.totalChannels - 1);
                this.channelArray = var2;
                this.pollWrapper.grow(var1);
            }
    
            if (this.totalChannels % 1024 == 0) {
                this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, this.totalChannels);
                ++this.totalChannels;
                ++this.threadsCount;
            }
            
        }
    

    このchannelArrayは最初は固定初期化サイズが8であったが、totalChannelsは最初は1で最大1024であった.これは後の操作を容易にするためであり、channelArrayでは0と表記された要素は使用されず、直接下から1と表記されている.totalChannelsが配列長に等しい場合は、拡張を行い、配列要素をコピーし、pollWrapperを拡張します.
     void grow(int var1) {
            PollArrayWrapper var2 = new PollArrayWrapper(var1);
    
            for(int var3 = 0; var3 < this.size; ++var3) {
                this.replaceEntry(this, var3, var2, var3);
            }
    
            this.pollArray.free();
            this.pollArray = var2.pollArray;
            this.size = var2.size;
            this.pollArrayAddress = this.pollArray.address();
        }
    

    論理は簡単で、元のsocketハンドルfdValとイベント応答eventsを新しいPollArrayWrapperオブジェクトにコピーし、位置が変わらず、最後に各関連メンバーに値を再割り当てます.
    totalChannelsが1024の整数倍の場合、WindowsSelectorImplの構築方法でも呼び出されたaddWakeupSocketメソッドが呼び出されます.
    	 WindowsSelectorImpl(SelectorProvider var1) throws IOException {
            super(var1);
            this.wakeupSourceFd = ((SelChImpl)this.wakeupPipe.source()).getFDVal();
            SinkChannelImpl var2 = (SinkChannelImpl)this.wakeupPipe.sink();
            var2.sc.socket().setTcpNoDelay(true);
            this.wakeupSinkFd = var2.getFDVal();
            this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);
        }
    	void addWakeupSocket(int var1, int var2) {
            this.putDescriptor(var2, var1);
            this.putEventOps(var2, Net.POLLIN);
        }
    

    受信var 1は、selectorが最初に開くときに生成されるsourcechannelの記述子fdvalであり、応答時間Netであることが明らかになった.POLLIN対応はOP_READ.
    拡張メソッドが終了したら、SelectionKeyImpl配列channelArrayにSelectionKeyImplが存在する場合に、現在のSelectionKeyImplオブジェクトに下付きラベルを追加します.次に、SelectionKeyImplオブジェクトをfdMapに格納し続け、fdMapが保存されている場合のChannelの記述子とSelectionKeyImplのマッピング関係:
    	private static final class MapEntry {
            SelectionKeyImpl ski;
            long updateCount = 0L;
            long clearedCount = 0L;
    
            MapEntry(SelectionKeyImpl var1) {
                this.ski = var1;
            }
        }
    	private static final class FdMap extends HashMap {
            static final long serialVersionUID = 0L;
    
            private FdMap() {
            }
    
            private WindowsSelectorImpl.MapEntry get(int var1) {
                return (WindowsSelectorImpl.MapEntry)this.get(new Integer(var1));
            }
    
            private WindowsSelectorImpl.MapEntry put(SelectionKeyImpl var1) {
                return (WindowsSelectorImpl.MapEntry)this.put(new Integer(var1.channel.getFDVal()), new WindowsSelectorImpl.MapEntry(var1));
            }
    
            private WindowsSelectorImpl.MapEntry remove(SelectionKeyImpl var1) {
                Integer var2 = new Integer(var1.channel.getFDVal());
                WindowsSelectorImpl.MapEntry var3 = (WindowsSelectorImpl.MapEntry)this.get(var2);
                return var3 != null && var3.ski.channel == var1.channel ? (WindowsSelectorImpl.MapEntry)this.remove(var2) : null;
            }
        }
    
    	private WindowsSelectorImpl.MapEntry put(SelectionKeyImpl var1) {
                return (WindowsSelectorImpl.MapEntry)this.put(new Integer(var1.channel.getFDVal()), new WindowsSelectorImpl.MapEntry(var1));
            }
    

    次にkeysのaddメソッドを呼び出して、現在のSelectionKeyImplオブジェクトを追加します.keysは親SelectorImplのオブジェクトです.
    protected HashSet keys = new HashSet();
    

    次にpollWrapperのaddEntryメソッドを呼び出します.
    	void addEntry(int var1, SelectionKeyImpl var2) {
            this.putDescriptor(var1, var2.channel.getFDVal());
        }
    

    この方法はchannelの記述子を追加する操作のみを行い,イベント応答は設定されていない.最後にtotalChannelsを自己増加し,implRegister法は終了した.
    SelectorImplクラスのregisterに戻り、implRegisterメソッドの終了後にinterestOpsを呼び出してイベント応答を設定し、現在のSelectionKeyオブジェクトに戻ってAbstractSelectableChannelのregisterメソッドのkに値を割り当て、addKeyメソッドを呼び出し、kを入力する.
    private void addKey(SelectionKey k) {
            assert Thread.holdsLock(keyLock);
            int i = 0;
            if ((keys != null) && (keyCount < keys.length)) {
                // Find empty element of key array
                for (i = 0; i < keys.length; i++)
                    if (keys[i] == null)
                        break;
            } else if (keys == null) {
                keys =  new SelectionKey[3];
            } else {
                // Grow key array
                int n = keys.length * 2;
                SelectionKey[] ks =  new SelectionKey[n];
                for (i = 0; i < keys.length; i++)
                    ks[i] = keys[i];
                keys = ks;
                i = keyCount;
            }
            keys[i] = k;
            keyCount++;
        }
    

    コードの論理は比較的簡単で、詳しくは言わないで、channeの登録はこれで終わります.
  • 2.4 configureBlocking(boolean block)configureBlockingメソッドはAbstractSelectableChannelクラスで実装される.

  • この方法は、NIOがブロッキングモードと非ブロッキングモードの両方をサポートするため、ブロッキングまたは非ブロッキングモードの設定である.NIOの非ブロッキングモードは,ネットワークプログラミングにおける大量のネットワーク通信の処理においてBIOよりも優れている.
     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;
        }
    

    最初のifは、現在のチャンネルが開いているかどうかを判断するために使用され、開いていない場合、異常が放出される.2番目のifは、設定されたブロックモードであるか否かを判断するために使用される(デフォルトblocking=true).3番目のif:2番目のif判断は成立しないが、3番目のif判断が成立する前提条件は、blockがtrue、haveValidKeys()がtrueであれば異常を投げ出すことである.では、説明する前に非ブロックモードが設定されていた.
    いずれも成立しない場合は、ブロックモードを設定します.implConfigureBlocking()は、異なるチャネルにおいて異なる実装スキームを有する.そして最後に底層の操作について、ここでは簡単な理解しかしません.
    haveValidKeys()メソッドは、AbstractSelectableChannelクラスで実装されます.コードは次のとおりです.
    private boolean haveValidKeys() {
            synchronized (keyLock) {
                if (keyCount == 0)
                    return false;
                for (int i = 0; i < keys.length; i++) {
                    if ((keys[i] != null) && keys[i].isValid())
                        return true;
                }
                return false;
            }
        }
    

    コードの論理は比較的簡単で、分析をしません.以上の3つのifの判定が成立しない場合は,implConfigureBlockingメソッドを呼び出してブロックモードに設定し,implConfigureBlockingメソッドは異なるchannelで異なる実装方式があり,最終的な実装はnativeメソッドであり,ここでは深く検討しない.