NIO 구성 요소 Buffer, Channel 및 Selector

흐름은 데이터를 읽고 쓰는 데 쓰인다.모든 I/O는 하나의 바이트로 간주되며 Stream이라는 객체를 통해 한 번에 한 바이트씩 이동합니다.스트림 I/O는 외부 세계와의 접촉에 사용됩니다.또한 객체를 바이트로 변환한 다음 다시 객체로 변환하는 데 내부적으로 사용됩니다.
흐름과 NIO의 가장 중요한 차이점은 데이터가 포장되고 전송되는 방식이다. 원래의 I/O는 흐르는 방식으로 데이터를 처리하고 NIO는 블록으로 데이터를 처리한다.
흐름과 블록의 비교
  • 스트리밍을 위한 I/O 시스템은 데이터를 한 번에 한 바이트씩 처리하기 때문에 우리는 쉽게 스트리밍으로 포장하여 원하는 작업을 완성할 수 있다
  • 블록 기반 I/O 시스템은 블록으로 데이터를 처리합니다.모든 조작은 한 걸음 한 걸음 데이터 블록을 생성하거나 소비한다.블록 처리 데이터는 바이트 처리 데이터보다 훨씬 빠르다.그러나 블록 기반 I/O에는 스트림 기반 I/O의 우아함과 단순성이 부족합니다
  • .

    버퍼


    NIO의 데이터는 모두 채널에서 버퍼로 읽히고 버퍼에서 채널로 기록됩니다
    버퍼는 본질적으로 데이터를 쓸 수 있고, 그 중에서 데이터를 읽을 수 있는 메모리이다.이 메모리는 NIO Buffer 개체로 포장되어 있으며 블록 메모리에 쉽게 접근할 수 있는 방법을 제공합니다
    1. NIO의 Buffer는 다음과 같은 이점을 제공합니다.
  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

  • 이러한 버퍼 유형은 서로 다른 데이터 유형을 나타냅니다.다시 말하면char,short,int,long,float 또는double 형식을 통해 버퍼의 바이트를 조작할 수 있다
    가장 핵심적인 것은 Byte Buffer이다. 앞에 있는 큰 종류는 포장만 했을 뿐이다. 우리가 가장 많이 사용하는 것도 Byte Buffer이다. Buffer를 하나의 수조로 이해할 수 있다. Int Buffer,Char Buffer,Double Buffer 등은 각각 int[],char[],double[] 등에 대응한다.
    2. Buffer의 중요 속성:
  • position
  • limit
  • capacity

  • capacity는 버퍼의 용량을 대표합니다. 변경할 수 없습니다.예를 들어capacity가 1024인 IntBuffer는 한 번에 1024개의 int 형식의 값을 저장할 수 있음을 나타낸다.Buffer의 용량이 capacity에 도달하면 Buffer를 비워야 다시 쓰기 가능
    우선, 읽기와 쓰기 조작이라는 개념에 대해 먼저 설명해야 한다. 그렇지 않으면 혼동될 수 있다.시스템 수준:
  • Channel에서 Buffer
  • 로 데이터 읽기
  • Buffer에서 Channel
  • 로 데이터를 쓰기
    Buffer의 경우:
  • 읽기 동작은 버퍼의 시작 위치에서 데이터를 읽는 것이다
  • 쓰기 작업은 Buffer에 데이터를 쓰거나 채우는 것
  • 다음 읽기와 쓰기 동작에서position과 limit을 관찰하는 것은 버퍼 각도에서 말하는 것이다
    position, 다음 쓰기 위치를 나타냅니다.초기값은 0입니다. 버퍼에 값을 쓸 때마다position은 자동으로 1을 추가하고, 읽을 때마다position은 자동으로 1을 추가합니다.
    쓰기 모드에서 읽기 모드로 전환할 때 (flip),position은 0으로 돌아갑니다
    limit, 쓰기 동작 모드에서 limit은 쓰기 가능한 최대 데이터를 대표합니다.이때limit은capacity와 같다.쓰기가 끝난 후 읽기 모드로 전환합니다. 이 때의 limit은 버퍼의 실제 데이터 크기와 같습니다. 버퍼가 가득 쓰여 있지 않기 때문입니다.
    3. Buffer 객체 할당
    Buffer 객체 초기화
    ByteBuffer byteBuf = ByteBuffer.allocate(1024);
    IntBuffer intBuf = IntBuffer.allocate(1024);
    

    Buffer에 데이터 쓰기:
  • 버퍼의put() 방법을 통해 버퍼에 쓰기
  • Channel에서 Buffer
  • 로 쓰기
    put 방법에 대해 다음과 같은 함수 정의가 있다
    //   byte  
    public abstract ByteBuffer put(byte b);
    //   int  
    public abstract ByteBuffer put(int index, byte b);
    //  
    public final ByteBuffer put(byte[] src) {...}
    

    이러한 방법은 스스로 버퍼의 크기를 조절해야 한다.capacity를 초과해서는 안 되고,java를 던질 줄 아는 것을 초과해서는 안 된다.nio.BufferOverflowException 예외
    Channel을 사용하여 Buffer에 데이터를 씁니다. 시스템 차원에서 이 동작을 우리는 읽기 조작이라고 합니다. 왜냐하면 데이터는 외부 (파일이나 네트워크 등) 에서 메모리로 읽히기 때문입니다.
    int bytesRead = inChannel.read(buf); // Buffer 
    

    Buffer에서 데이터를 읽습니다.Buffer의 값을 읽으려면 쓰기 모드에서 읽기 모드로 전환하고flip 방법은 Buffer를 쓰기 모드에서 읽기 모드로 전환합니다.flip () 방법은position을 0으로 설정하고limit을 이전position의 값, 즉 Buffer의 실제 데이터 크기로 설정합니다.
    public final Buffer flip() {
        limit = position; //   limit  
        position = 0; //   position   0
        mark = -1; 
        return this;
    }
    
  • get () 방법으로 Buffer에서 데이터를 읽기
  • Buffer에서 Channel
  • 로 데이터 읽기
    get 방법은 여러 개를 다시 불러옵니다. 버퍼에서 다른 방식으로 데이터를 읽을 수 있습니다.
    //   position  
    public abstract byte get();
    //  
    public abstract byte get(int index);
    //   Buffer  
    public ByteBuffer get(byte[] dst)
    
    byte aByte = buf.get();
    

    예를 들어 FileChannel을 통해 데이터를 파일에 쓸 수 있고 SocketChannel을 통해 데이터를 네트워크에 써서 원격 기기에 보낼 수 있다
    int bytesWritten = channel.write(buf);
    

    4. 중요한 방법
    mark() and reset()
    mark는position의 값을 임시로 저장하는 데 사용되며, 매번 mark () 방법을 호출할 때마다 mark의 값을 현재position으로 설정하여 나중에 필요할 때 사용합니다.뒤에 버퍼를 호출할 수 있습니다.reset () 방법으로 이position으로 복구
    public final Buffer mark() {
        mark = position;
        return this;
    }
    
    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }
    

    rewind() clear() compact()
    rewind ():position을 0으로 초기화합니다. 보통 Buffer를 처음부터 다시 읽는 데 사용됩니다.
    public final Buffer rewind() {
            position = 0;
            mark = -1;
            return this;
        }
    

    clear (): 버퍼를 리셋합니다. 다시 실례화하는 것과 같습니다.position은 0으로 설정되고 limit은capacity의 값으로 설정됩니다.clear () 방법은 버퍼의 데이터를 비우지 않습니다. 다만 후속 쓰기는 원래의 데이터를 덮어쓰고 비우는 것과 같습니다.
    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
    

    compact (): 읽지 않은 데이터, 즉 포지션에서 limit 사이의 데이터 (읽지 않은 데이터) 를 처리하고, 읽지 않은 모든 데이터를 버퍼의 시작점으로 복사한 다음, 마지막 읽지 않은 요소의 다음 위치로 포지션을 설정하고, 이 기초에 다시 쓰기 시작합니다.이 때 limit은capacity와 같아서 새 데이터를 쓰면 원래 데이터를 덮어쓰지 않습니다

    Channel


    Channel은 데이터를 읽고 쓸 수 있는 객체입니다.NIO와 기존 I/O를 비교하면 채널이 흐름과 같습니다.Channel과 Buffer는 상호작용을 하며, 읽기 동작은 Channel의 데이터를 Buffer에 채우고, 쓰기 동작은 Buffer의 데이터를 Channel에 쓴다
    NIO 구현 Channel:
  • FileChannel: 파일 읽기 및 쓰기를 위한 파일 채널
  • DatagramChannel: UDP 연결용 수신 및 전송
  • SocketChannel: 이를 TCP 연결 채널로 이해하고 쉽게 이해하면 TCP 클라이언트
  • ServerSocketChannel: TCP에 대응하는 서비스 포트, 어떤 포트가 들어오는 요청을 감청하는 데 사용
  • Channel 사용
    channel.read(buffer);
    channel.write(buffer);
    

    FileChannel
    FileChannel은 비차단 초기화를 지원하지 않습니다.
    // 
    FileInputStream inputStream = new FileInputStream(new File("/data.txt"));
    FileChannel fileChannel = inputStream.getChannel();
    
    // RandomAccessFile
    RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
    FileChannel inChannel = aFile.getChannel();
    
    

    Buffer에서 파일 내용 읽기
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    
    int num = fileChannel.read(buffer);
    

    Channel에 파일 컨텐츠 쓰기
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("  Buffer  ".getBytes());
    // Buffer  
    buffer.flip();
    while(buffer.hasRemaining()) {
        //   Buffer  
        fileChannel.write(buffer);
    }
    

    SocketChannel ServerSocketChannel
    TCP 접속 채널
    //  
    SocketChannel socketChannel = SocketChannel.open();
    //  
    socketChannel.connect(new InetSocketAddress("https://www.javadoop.com", 80));
    
    //  
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    //   8080  
    serverSocketChannel.socket().bind(new InetSocketAddress(8080));
    
    while (true) {
       //   TCP  ,  SocketChannel  
       SocketChannel socketChannel = serverSocketChannel.accept();
    }
    

    ServerSocketChannel은 Buffer와 접촉하지 않습니다. 데이터를 실제적으로 처리하지 않기 때문입니다. 요청을 받은 후에 SocketChannel을 실례화한 후에 이 연결 채널의 데이터 전달은 상관하지 않습니다. 포트를 계속 감청하고 다음 연결을 기다려야 하기 때문입니다.

    3. Selector


    선택기, Selector는 자바 NIO에서 여러 개의 NIO 채널을 감지하고, 읽기, 쓰기 이벤트와 같은 채널을 위한 준비가 되어 있는지 알 수 있는 구성 요소입니다.하나의 단독 라인으로 여러 개의 채널을 관리할 수 있어 여러 개의 네트워크 연결을 관리할 수 있다.Selector 는 다양한 I/O 이벤트에 대한 관심 분야를 등록하며, 이러한 이벤트가 발생하면 그 대상이 발생하는 이벤트를 알려줍니다.
    // Selector
    Selector selector = Selector.open();
    //  , 
    channel.configureBlocking(false);
    
    // Selector 
    SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
    

    register 방법의 두 번째 int형 매개 변수(이진법의 표시 위치를 사용)는 어떤 관심 있는 사건을 감청해야 하는지를 나타내는 데 사용된다. 총 네 가지 사건이다.
  • SelectionKey.OP_00000001용 READ, 채널에서 읽을 수 있는 데이터 있음
  • SelectionKey.OP_WRITE, 00000100, 채널에 데이터를 쓸 수 있음
  • SelectionKey.OP_CONNECT, 00001000, TCP 연결 성공
  • SelectionKey.OP_ACCEPT, 00010000용 TCP 연결 적용
  • 채널에서 발생하는 여러 이벤트를 동시에 감청할 수 있습니다. 예를 들어 ACCEPT와 READ 이벤트를 감청하려면 이진법의 00010001 즉 10진수 값 17을 지정하면 됩니다.
    등록 메서드 반환 값은 SelectionKey 인스턴스이며 다음 항목을 포함합니다.
  • Channel
  • Selector
  • Interest Set, 즉 우리가 설정한 관심 있는 감청 중인 이벤트 집합
  • ready 집합, 즉 대응하는 집합의boolean 값
  • 을 되돌려줍니다
    관련 방법
    select () 방법, select () 방법이 되돌아오는 int값은 채널이 얼마나 준비되었는지 표시합니다.이 메서드를 사용하면 마지막 select 이후에 준비된 채널에 해당하는 SelectionKey를 selected set에 복사합니다.만약 어떤 통로가 준비되지 않았다면, 이 방법은 최소한 하나의 통로가 준비될 때까지 막힐 것이다
    selectNow() 기능은 select와 마찬가지로 준비된 채널이 없으면 이 방법은 즉시 0으로 되돌아옵니다
    select (long timeout) 채널이 준비되지 않으면 이 방법은 잠시 기다립니다

    좋은 웹페이지 즐겨찾기