원 스톱 학습 자바 네트워크 프로그래밍 BIO_NIO_AIO의 포괄적 인 이해, 학습 노트 (7)

안녕하세요 여러분, 저는方圆


1. 시스템 수준에서 IO 모델 분석

네트워크 또는 기타 프로세스에서 데이터를 수신하면이 데이터가 에 복사 된 系统内核的缓冲区다음 커널 버퍼에서 우리로 복사 되어 应用程序对应的缓冲区애플리케이션에서이 데이터를 얻을 수 있습니다.

1.1 BIO 모델

여기에 사진 설명 삽입

  • 먼저 애플리케이션이 调用特定的函数운영 체제에 액세스 할 수 있도록합니다. BIO 채팅방을 예로 들어 보겠습니다. 우리는 서버에 있고 네트워크에서 클라이언트가 전송 한 데이터가 준비되었는지 확인하고 싶습니다. 그러면 새 데이터를 수신하지 않았는지 운영 체제에 묻습니다. , 이 호출이 성공적으로 반환 될 수 있도록 一直阻塞메시지가 수신되고 수신 될 때까지 여기에 从系统内核缓冲区中拷贝到应用程序的缓冲区있습니다. IO를 차단하고 있습니다. 기다리는 동안 아무것도 할 수 없습니다.

1.2 NIO 모델

여기에 사진 설명 삽입

  • 응용 프로그램이 시스템 호출을 할 때 데이터가 준비되었는지 여부를 묻습니다. 준비가되지 않은 경우 非阻塞的이므로 直接返回시스템이 커널 버퍼의 데이터를 응용 프로그램 버퍼에 복사 할 때까지 데이터가 준비되었는지 물어 보면 원하는 데이터를 얻을 수 있습니다. 그러나 선택기 모니터링 모드를 포함하지 않으며 NIO의 비 차단 모델 일뿐입니다.

1.2.1 IO 멀티플렉싱

여기에 사진 설명 삽입

  • 이 모드는 선택기 모니터링을 사용하여 NIO 채팅방에서 사용되는 모드에 해당합니다.
  • 우선, 우리의 응용 프로그램은 새로운 쿼리를 시작하고, 현재 데이터가 준비되지 않은 경우 작동에 사용할 수있는 데이터가 있는지 여부를 확인 并不会如上NIO直接返回하지만 监听데이터를 사용할 수있을 때까지 커널에 IO 채널을 요청합니다. 이 프로그램은 작동, 그래서 우리가이 모니터링 과정이 우리의 채팅 방에서 같은 것을 알려 select()方法, 그것은이다 阻塞. 데이터가 시스템 커널 캐시에 준비 될 때까지 시스템 호출을 실행하고 캐시의 데이터를 응용 프로그램 캐시에 복사 할 수 있음을 알려줍니다. 그러면 우리가 원하는 것을 실제로 얻을 수 있습니다. 자료
  • 이때 시스템 커널은 여러 IO 채널을 모니터링 할 수 있습니다. 채팅방과 마찬가지로 여러 채널도 모니터링합니다. IO 채널 중 하나에 새로운 상태 업데이트가있는 한 모니터링은 애플리케이션으로 반환됩니다. 하나 이상의 IO 채널이 상태를 변경했다고 가정 해 보겠습니다. 처리 하시겠습니까? 반환되는 조건에 따라 특정 처리를 수행 할 수 있습니다. (선택기를 IO 멀티플렉서로 변환 할 수 있음)

1.3 AIO 모델 (비동기 IO)

여기에 사진 설명 삽입

  • BIO와 NIO는 모두 동기식 IO 모델입니다. 여기서는 AIO (비동기) 모델에 대해 설명합니다.
  • 동기식 IO 모델은 액세스하는 데이터가 준비되었는지 여부에 따라 결과를 반환합니다. 데이터가 준비되지 않은 경우 데이터를 가져올 수 없습니다 如果我们再也不发起获取数据的请求,那么我们永远都不会再获取到这个数据. 비동기 IO가 다릅니다.이 데이터를 요청하면 요청되지 않지만 之后这个数据准备好了,它就会回去通知你이 데이터를 가져올 수 있습니다.
  • 이 프로세스를 살펴 보겠습니다. 데이터를 요청하고 데이터가 준비되지 않았으며 차단되지 않고 대신 直接返回. 응용 프로그램 수준에서는 새 요청을 시작하지 않았지만 시스템 백그라운드에서 요청한 데이터의 상태를 모니터링합니다. 필요한 데이터가 준비되고 이미 시스템 커널 캐시에 있으면 시스템은 백그라운드에서이 데이터를 애플리케이션 캐시 영역에 복사합니다. 여기서 시스템 커널은 递交给我们一个信号이전에 원하는 데이터가 준비되어 있으며 사용할 수 있음을 알려줍니다.
  • 되어 异步우리의 프로그램은 한 번만 데이터에 대한 요청을 시작하고, 어떤 요청이 수신되지 않는 경우, 그것은 직접 반환하는 데이터가 준비되면 그 후, 시스템이 다시 올 것이다 다시 요청을 시작하는 우리를 필요로하지 않고 알려 : 구현.. 이 데이터는 비동기 특성을 반영하여 얻을 수 있습니다. A는 비동기를 의미합니다.

2. 비동기 호출 메커니즘

2.1 AIO의 비동기 작업

여기에 사진 설명 삽입

  • 클라이언트는 AsynchronousSocketChannel에 해당합니다.
  • 서버는 AsynchronousServerSocketChannel에 해당합니다.
  • 연결 / 수락으로 연결 설정
  • 읽기 작업을 읽습니다.
  • 쓰기 작업은 쓰기입니다.

2.2 Future를 통한 비동기 호출

여기에 사진 설명 삽입

  • Future의 get () 메소드는

2.3 CompletionHandler를 통해 (다목적)

여기에 사진 설명 삽입

  • 작업을 수행 할 때 CompletionHandler 매개 변수를 전달합니다.

3. 실제 전투 (에코 서버)

3.1 서버 측

3.1.1 필드

여기에 사진 설명 삽입

3.1.2 주요 방법

여기에 사진 설명 삽입

3.1.3 AcceptHandler 구현

여기에 사진 설명 삽입

3.1.4 ClientHandler 구현

여기에 사진 설명 삽입

3.2 클라이언트

3.2.1 필드

여기에 사진 설명 삽입

3.2.2 주요 방법

여기에 사진 설명 삽입


4. 코드

4.1 서버 측

import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.HashMap;
import java.util.Map;

public class Server {
    
    
    private static final String LOCALHOST = "localhost";
    private static final int DEFAULT_PORT = 8888;

    private AsynchronousServerSocketChannel serverChannel;

    private void close(Closeable closeable){
    
    
        try {
    
    
            closeable.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    public void start(){
    
    
        try {
    
    
            //绑定端口号,调用的open方法(无参),这个参数类型为AsynchronousChannelGroup,其中包含共享的系统资源,如线程池,
            //因为我们没有传入参数,会从默认的ProviderHolder中,提供一个我们需要的AsynchronousServerSocketChannel对象
            //Handler会在不同的线程中进行处理,如我们的AcceptHandler和ClientHandler,它就是动用的共享资源:线程,来执行
            serverChannel = AsynchronousServerSocketChannel.open();
            serverChannel.bind(new InetSocketAddress(LOCALHOST,DEFAULT_PORT));
            System.out.println("服务器启动成功,监听端口号:" + DEFAULT_PORT);

            while (true){
    
    
                //该accept方法为异步调用,没有需要返回的结果也会返回,即没有收到客户连接的请求时
                //就会返回结果了;但是我们要保证在有客户连接的时候,主线程还在运行,否则主线程返回
                //服务器就直接宕机了,我们采用下面的小技巧来避免这种情况

                //accept在系统层面完成的时候(系统帮助我们完成了这个IO处理),返回的结果会被AcceptHandler来处理,
                //成功时执行的completed方法,失败执行的是failed方法
                //附带对象无;AcceptHandler为实现接口CompletionHandler的类,处理accept请求
                serverChannel.accept(null,new AcceptHandler());

                //用这个来避免while过于频繁,相当于将主线程阻塞,以保证建立连接时与客户端的响应
                System.in.read();
            }


        } catch (IOException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            close(serverChannel);
        }
    }

    /**
     * 程序处理accept请求的时候,并不是在主线程中执行的,
     * 而是从AsynchronousChannelGroup中取出另一个线程来执行
     * CompletionHandler泛型为,第一个为返回的结果类型;第二个为附带的对象类型
      */
    private class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel,Object> {
    
    
        /**
         * completed 该方法对应的是,我们之前上方调用accept方法,正常返回了,那么会执行该方法
         * @param result 方法执行成功的返回值
         * @param attachment 附带信息
         */
        @Override
        public void completed(AsynchronousSocketChannel result, Object attachment) {
    
    
            if(serverChannel.isOpen()){
    
    
                //确保服务器还在运行
                //服务器继续等待下一个客户端来请求,但是这里并不是产生过多的accept方法压栈
                //而造成的栈溢出问题,这在底层已经进行保护了
                serverChannel.accept(null,this);
            }

            //处理已连接客户端的读写操作
            AsynchronousSocketChannel clientChannel = result;
            if(clientChannel != null && clientChannel.isOpen()){
    
    
                ClientHandler handler = new ClientHandler(clientChannel);
                ByteBuffer buffer = ByteBuffer.allocate(1024);

                Map<String,Object> attachmentInfo = new HashMap<>();
                attachmentInfo.put("type","read");
                attachmentInfo.put("buffer",buffer);

                //依靠ClientHandler异步处理,读写操作,将其回传给客户端
                clientChannel.read(buffer,attachmentInfo,handler);
            }

        }

        @Override
        public void failed(Throwable exc, Object attachment) {
    
    
            //失败时的调用
        }
    }

    private class ClientHandler implements CompletionHandler<Integer,Map<String,Object>> {
    
    
        private AsynchronousSocketChannel clientChannel;

        public ClientHandler(AsynchronousSocketChannel clientChannel) {
    
    
            this.clientChannel = clientChannel;
        }

        @Override
        public void completed(Integer result, Map<String, Object> attachment) {
    
    
            String type = (String) attachment.get("type");
            ByteBuffer buffer = (ByteBuffer) attachment.get("buffer");
            if("read".equals(type)){
    
    
                //已经读取到了客户端传过来的消息,将其回音给客户端
                buffer.flip();//回音要读缓冲区
                attachment.put("type","write");
                clientChannel.write(buffer,attachment,this);
            }else if ("write".equals(type)){
    
    
                //将其回传给客户端后,等待客户端的新的信息
                //将这里将再次进行异步调用,读取客户端发来的信息存储在缓冲区中
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                attachment.put("type","read");
                attachment.put("buffer",byteBuffer);
                clientChannel.read(byteBuffer,attachment,this);
            }
        }

        @Override
        public void failed(Throwable exc, Map<String, Object> attachment) {
    
    

        }
    }

    public static void main(String[] args) {
    
    
        Server server = new Server();
        server.start();
    }
}

4.2 클라이언트

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class Client {
    
    

    private final String LOCALHOST = "localhost";
    private final int DEFAULT_PORT = 8888;

    AsynchronousSocketChannel clientChannel;

    private void close(Closeable closeable){
    
    
        try {
    
    
            closeable.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    public void start(){
    
    
        try {
    
    
            clientChannel = AsynchronousSocketChannel.open();

            Future<Void> connect = clientChannel.connect(new InetSocketAddress(LOCALHOST, DEFAULT_PORT));
            connect.get();//阻塞式调用,直到有结果才返回

            //读取用户的输入
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

            while (true){
    
    
                String input = in.readLine();
                byte[] inputBytes = input.getBytes();
                ByteBuffer buffer = ByteBuffer.wrap(inputBytes);

                //向服务器发送消息
                Future<Integer> write = clientChannel.write(buffer);
                write.get();

                //接收服务器传来的消息
                buffer.flip();
                Future<Integer> read = clientChannel.read(buffer);
                read.get();

                String s = new String(buffer.array());
                System.out.println(s);

                buffer.clear();
            }


        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            close(clientChannel);
        }
    }

    public static void main(String[] args) {
    
    
        Client client = new Client();
        client.start();
    }
}

5. 테스트 결과

여기에 사진 설명 삽입


어서!

추천

출처blog.csdn.net/qq_46225886/article/details/107545116