NIO-Buffer、Channel、Selector

21868 ワード

Buffer


Bufferは本質的にメモリの1つで、データを書き込んだり取得したりすることができます.
java.NioはCharBufferShortBufferIntBufferLongBufferFloatBufferDoubleBufferByteBuffer->MappedByteBufferの実装を定義し、コアはByteBufferである.対応する基本タイプとして理解できる配列.

Bufferの重要な属性-position、limit、capacity

  • capacity-バッファ(配列)の容量です.bufferの容量がcapacityに達すると、bufferを空にして値を書き直す必要があります.
  • position-初期値は0であり、Bufferに1つの値が書き込まれるごとにpositionは+1となり、読み取り操作の際も1つの値が読むごとにpositionは+1となる.
  • Limit-書き込み動作モードでは最大書き込み可能なデータを表し、このときLimit=capacityとなる.読み取り動作モードでのLimit=Bufferの実際のデータサイズ.

  • flip()メソッド-bufferを書き込みモードから読み取りモードに切り替えるためにのみ使用され、読み取り時に呼び出さなければデータが読めません.
    なお、通常、NIOの読み出し動作については、チャネルからBufferにデータを読み出し、Bufferへの書き込み動作に対応している.
    public final Buffer flip() {
        limit = position; //   limit  
        position = 0; //   position   0
        mark = -1; 
        return this;
    }
    

    Bufferの初期化

    allocate(int capacity)は、1つのBufferをインスタンス化することができ、さらにwrapメソッドは、byte[]パラメータを受信するBufferを初期化することもできる.

    Bufferの充填


    putメソッドを使用してBufferを埋めます.byte、int(indexの位置を指定)およびbyte、byte[]のパラメータを受信することができ、Bufferサイズがcapacityを超えないように制御する必要がある.
    あるいはchannelからのデータをBufferに埋め込み、前述したようにシステムレベルではNIOと呼ばれる読み取り動作を行う.
    int num = channel.read(buffer);
    

    チャンネルからBufferに読み込まれたデータサイズを返します.

    フェッチBuffer


    まずモードを切り替え,flip()メソッド,すなわちpositionとlimitを切り替える.一連のputメソッドに対応して、positionに従ってデータを取得するbyte get()や、指定された位置データを取得するbyte get(int index)、buffer中のデータを配列に書き込むByteBuffer get(byte[]dest)などの一連のgetメソッドもある.
    さらによく使われるのは、Bufferの値を様々なチャンネルで対応する位置に書き込む書き込み操作です.
    int num = channel.write(buffer);
    

    mark() reset()


    bufferのMark属性は主にPositionの値を一時的に保存するため,mark()メソッドを提供してmarkの値を現在のpositionに設定する.その後、必要に応じてreset()メソッドを呼び出し、Positionがmarkの場所に戻ることができます.

    rewind() clear() compact()


    rewind():positionを0にリセットし、markを-1に設定し、bufferを最初から読み書きすることができます.
    clear():bufferの主なプロパティを再初期化します.bufferを再インスタンス化することに相当します.一般的にbufferを再充填する前にclear()を呼び出します.
    clear()はbufferのデータを空にするのではなく、bufferの主なプロパティを初期化しただけです.そのため、後から書き換えたデータはposition=0の位置で書き換えられ、データが空になったことに相当します.
    compact():bufferが新しいデータを書き込む前に呼び出す準備もできています.しかし、positionからlimitまでのデータを左に移動し、その上で書き込みを開始します.このときlimitはcapacityに等しく,positionは元のデータの右を指す.
    具体的なcompact()実装:
    // position limit 。
    public LongBuffer compact() {
    
            System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
            position(remaining());// position limit-position
            limit(capacity());// limit capacity
            discardMark();// mark -1
            return this;
    }
    

    Channel


    channelはデータソースまたはデータ書き込みの目的地javaである.NioパッケージにはFileChannelSocketChannelDatagramChannelServerSocketChannelが実装されています.
    次のようになります.
  • FileChannelファイルの読み書き用(少ない、nioパッケージの主な注目先ではなく、非ブロックをサポートしない)
  • DatagramChannel UDPの接続および送信のための
  • SocketChannelは、TCP接続のためのものであり、TCPクライアント
  • と理解することができる.
  • ServerSocketChannelは、TCP対応のサービス・エンドに使用され、あるポートからの要求をリスニングします.

  • SocketChannelとServerSocketChannelに注目します.
    ChannelはIOのストリームに類似しており、読み出し操作時にはbufferにchannelのデータが埋め込まれ、書き込み操作時にはBufferのデータがchannelに書き込まれる.
    なお、NIOレベルでの「読み出し」操作と「書き込み」操作は、チャネルに対して、すなわち「読み出し」はチャネルからbufferへ、「書き込み」はbufferからchannelへ(対応するchannelへの書き込み)である.一方、bufferに対応する操作は正反対であり、「読み取り」はbufferの「書き込み」に対応し、「書き込み」はbufferの「読み取り」に対応する.
    読み取り操作:channel.read(buffer); channelからbufferにデータを読み込み、後続処理を行います.
    書き込み操作:channel.write(buffer); Bufferからchannelにデータを書き込みます.
    ※どちらの方法もチャネルの実例方法です.※すべてのchannelはbufferとインタラクティブです.

    SocketChannel


    TCP接続を開きます.
    SocketChannel sc = SocketChannel.open();
    sc.connect(new InetSocketAddress("http://www.baidu.com", 80));
    
    // 
    sc.read(buffer);
    // 
    while(buffer.hasRemaining()) {
    	sc.write(buffer);
    }
    

    後続の部分は、Server SocketChannelでまた見ます.

    ServerSocketChannel


    マシンポートを傍受し、このポートから入るTCP接続を管理するために使用されます.
    ServerSocketChannel ssc = ServerSocketChannel.open();
    // 8080
    ssc.socket().bind(new InetSocketAddress(8080));
    
    while(true) { 
    	// TCP , socketChannel 。
    	SocketChannel sc = ssc.accept();
    }
    

    DatagramChannel


    DatagramChannelクラス処理サービス側とクライアント.
    // 
    DatagramChannel dc = DatagramChannel.open();
    dc.socket().bind(new InetSocketAddress(9090));
    
    ByteBuffer byf = ByteBuffer.allocate(48);
    ddc.receive(buf);
    
    // 
    String data = "new Data";
    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.put(data.getBytes());
    buf.flip();// buffer 
    int byteSent = dc.send(buf, new InetSocketAddress("anyuri"), 80);
    

    Selector


    NIOのマルチプレクサは、1つのスレッドで複数のchannelを管理するためのものである.Selectorは非ブロックに基づいて構築する(FileChannelは使用できない).
    使用方法(基本的なインタフェース操作):
    // Selector
    Selector selector = Selector.open();
    // Channel Selector 
    // 
    SocketChannel channel = SocketChannel.open(new InetSocketAddress("anyuri", 80));
    channel.configureBlocking(false);
    // 
    SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
    

    register(Selector s,int ops)の2番目のintパラメータは、リスニングが必要なタイプを表します.SelectionKeyでは、4種類の定数が定義されています.
    OP_READ = 1 << 0; (00000001) OP_WRITE = 1 << 2; (00000100) OP_CONNECT = 1 << 3; (00001000) OP_ACCEPT = 1 << 4; (00010000)
    readとacceptイベントを同時にリスニングするにはopsをOP_として指定するなど、複数のイベントを同時にリスニングできます.READ+OP_ACCEPTでいいです.
    channelは、メソッドを登録してselectionKeyインスタンスを返します.
    次にselect()メソッドを呼び出してチャネル情報を取得します.
    手順をレビュー:
  • selector
  • を開く
  • 登録channel
  • 呼び出しselect()
  • Selectorの他の方法:
  • select()-準備されたchannelに対応するSelectionKeyをselected setにコピーし、channelの準備ができていない場合、少なくとも1つのchannelが準備されるまで、この方法はブロックされます.前回selectの後に用意されたchannel
  • を指す
  • selectNow()-上記の方法との違いは、準備されたchannelがない場合、その方法は直ちに0に戻る.
  • select(long timeout)-チャネルの準備ができていない場合、このメソッドはタイムアウト後にブロックされるのを待つ、実際のselect()メソッドはselect(0 L)を呼び出す.
  • wakeup()-select()およびselect(long timeout)に待機しているスレッドを起動します.wakeup()が先に呼び出され、スレッドがselect上でブロックされていない場合、その後のselect()またはselect(long timeout)はすぐに戻り、ブロックされず、この方法は1回しか機能しません.

  • Selector操作の簡単な例:
    SocketChannel sc = SocketChannel.open(new InetSocketAddress("anyuri", 80));
    sc.configureBlocking(false);
    Selector selector = Selector.open();
    SelectionKey key = sc.register(selector, SelectionKey.OP_READ);
    
    while(true) {
    	int readyChannelNum = selector.select();
    	if (readyChannelNum == 0) continue;
    
    	Set<SelectionKey> selectedKeys = selector.selectedKeys();
    	Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
    	while(keyIterator.hasNext()) {
    		if (key.isAcceptable()) {
    			// accept 
    		} else if (key.isConnectable()) {
    			// , 
    		} else if (key.isReadable()) {
    			// 
    		} else if (key.isWritable()) {
    			// 
    		}
    		// key
    		keyIterator.remove();
    	}
    }