자바 I/O 모델 의 발전 탐색

14914 단어 자바I/O모형.
관련 개념
동기 화 와 비동기
사용자 스 레 드 와 커 널 의 상호작용 방식 을 설명 합 니 다.
4.567917.동기 화 는 사용자 스 레 드 가 I/O 요청 을 한 후에 커 널 I/O 작업 이 완 료 된 후에 야 계속 실행 할 수 있다 는 것 을 말한다4.567917.비동기 란 사용자 스 레 드 가 I/O 요청 을 한 후에 도 계속 실행 하 는 것 을 말 합 니 다.커 널 I/O 작업 이 끝 난 후에 사용자 스 레 드 를 알 리 거나 사용자 스 레 드 가 등 록 된 리 셋 함 수 를 호출 합 니 다차단 과 비 차단
사용자 스 레 드 가 커 널 I/O 를 호출 하 는 방식 을 설명 합 니 다.
4.567917.차단 이란 I/O 작업 이 철저히 완 성 된 후에 야 사용자 공간 으로 돌아 가 는 것 을 말한다4.567917.비 차단 이란 I/O 작업 이 호출 된 후에 바로 사용자 에 게 상태 값 을 되 돌려 주 는 것 을 말 하 며 I/O 작업 이 완 료 될 때 까지 기다 릴 필요 가 없다하나의 I/O 작업 은 사실 두 단계 로 나 뉘 었 다.I/O 요청 과 실제 I/O 작업 을 시작 하 는 것 이다.차단 I/O 와 비 차단 I/O 의 차 이 는 첫 번 째 단계 에 있 습 니 다.I/O 요청 이 막 힐 수 있 는 지,완 료 될 때 까지 막 히 면 전통 적 인 차단 I/O 입 니 다.막 히 지 않 으 면 비 차단 I/O 입 니 다.동기 화 I/O 와 비동기 I/O 의 차 이 는 두 번 째 단계 가 막 히 는 지 여부 에 있 습 니 다.실제 I/O 읽 기와 쓰기 가 막 히 면 동기 화 I/O 입 니 다.
유 닉 스 I/O 모델
유 닉 스 아래 에는 모두 다섯 가지 I/O 모델 이 있다.
차단 I/O비 차단 I/OI/O 복용(select 와 poll)신호 구동 I/O(SIGIO)비동기 I/O(POSIX 의 ao계열 함수)차단 I/O
요청 이 즉시 완료 되 지 않 으 면 차단 을 유지 합 니 다.
단계 1:데이터 가 준비 되 기 를 기 다 립 니 다.네트워크 I/O 의 상황 은 원 격 데이터 가 속속 도착 하 기 를 기다 리 는 것 이다.디스크 I/O 의 경우 디스크 데이터 가 디스크 에서 커 널 메모리 로 읽 히 기 를 기다 리 는 것 입 니 다.
단계 2:데 이 터 를 커 널 에서 프로 세 스 로 복사 합 니 다.시스템 보안 때문에 사용자 상태의 프로그램 은 커 널 메모리 를 직접 읽 을 수 있 는 권한 이 없 기 때문에 커 널 은 커 널 메모리 의 데 이 터 를 사용자 상태 메모리 에 복사 하 는 것 을 책임 집 니 다.

비 차단 I/O
  • socket 을 NONBLOCK(비 차단)으로 설정 하 는 것 은 커 널 에 요청 한 I/O 작업 이 완료 되 지 않 을 때 프로 세 스 를 잠 들 지 말고 오류 코드(EWOULDBLOCK)로 되 돌려 주 는 것 입 니 다.그러면 요청 이 막 히 지 않 습 니 다.
  • 4.567917.I/O 조작 함 수 는 데이터 가 준비 되 었 는 지 계속 테스트 할 것 입 니 다.만약 에 준비 가 되 지 않 으 면 데이터 가 준 비 될 때 까지 계속 테스트 할 것 입 니 다.전체 I/O 요청 과정 에서 사용자 스 레 드 가 매번 I/O 요청 을 하면 바로 돌아 갈 수 있 지만 데 이 터 를 기다 리 기 위해 계속 문의 하고 중복 요청 을 해 야 하기 때문에 대량의 CPU 자원 을 소모 했다.
    4.567917.데 이 터 는 커 널 에서 사용자 공간 으로 복사 할 준비 가 되 어 있 습 니 다
    일반적으로 이런 모델 을 직접 사용 하지 않 고 다른 I/O 모델 에서 비 차단 I/O 라 는 특성 을 사용한다.이런 방식 은 단일 I/O 요청 에 큰 의미 가 없 지만 I/O 다 중 재 활용 에 도 로 를 평평 하 게 깔 았 다.
    I/O 재 활용(비동기 차단 I/O)
    I/O 다 중 재 활용 은 select 나 poll 함 수 를 사용 합 니 다.이 두 함 수 는 프로 세 스 를 막 을 수 있 지만 I/O 를 막 는 것 과 달리 이 두 함 수 는 여러 개의 I/O 작업 을 동시에 막 을 수 있 습 니 다.또한 여러 개의 읽 기 동작,여러 개의 쓰기 작업 의 I/O 함 수 를 동시에 검사 할 수 있 으 며,데이터 가 읽 을 수 있 거나 쓸 수 있 을 때 까지 I/O 작업 함 수 를 진정 으로 호출 할 수 있 습 니 다.

    프로 세 스 를 보면 select 함 수 를 사용 하여 I/O 요청 을 하 는 것 과 동기 화 블록 모델 은 큰 차이 가 없 으 며 감시 socket 을 추가 하고 select 함 수 를 호출 하 는 추가 작업 도 많아 효율 이 더욱 떨어진다.그러나 select 를 사용 한 후 가장 큰 장점 은 사용자 가 한 라인 에서 여러 socket 의 I/O 요청 을 동시에 처리 할 수 있다 는 것 이다.사용 자 는 여러 개의 socket 을 등록 한 후에 select 를 계속 호출 하여 활성 화 된 socket 을 읽 으 면 같은 스 레 드 에서 여러 개의 I/O 요청 을 동시에 처리 하 는 목적 을 달성 할 수 있 습 니 다.동기 화 블록 모델 에 서 는 다 중 스 레 드 방식 을 통 해 이 목적 을 달성 해 야 한다.
    I/O 다 중 복용 모델 은 Reactor 디자인 모델 을 사용 하여 이 메커니즘 을 실현 했다.
    select/poll 을 호출 하 는 방법 은 한 사용자 의 상태 스 레 드 가 여러 socket 을 돌아 가면 서 특정한 단계 1 의 데이터 가 완 료 될 때 까지 실제 사용자 스 레 드 실행 단계 2 의 복사 본 을 알려 줍 니 다.전문 적 인 사용자 상태 스 레 드 를 통 해 비 차단 I/O 폴 링 을 실행 하고 단계 1 의 비동기 화 를 모 의 했다.
    신호 구동 I/O(SIGIO)
    우선 socket 에서 신호 구동 I/O 를 허용 하고 신호 처리 함 수 를 설치 합 니 다.프로 세 스 가 계속 실행 되 는 것 은 막 히 지 않 습 니 다.데이터 가 준비 되 었 을 때 프로 세 스 는 SIGIO 신 호 를 받 고 신호 처리 함수 에서 I/O 조작 함수 로 데 이 터 를 처리 할 수 있 습 니 다.

    비동기 I/O
    aio 호출read 함수,커 널 설명 글자,버퍼 포인터,버퍼 크기,파일 오프셋 및 알림 방식 을 알려 주 고 즉시 돌아 갑 니 다.커 널 이 데 이 터 를 버퍼 에 복사 한 후에 프로그램 에 알 립 니 다.

    비동기 I/O 모델 은 Proactor 디자인 모델 을 사용 하여 이 메커니즘 을 실현 했다.
    커 널 에 전체 과정(단계 1 과 단계 2 포함)이 모두 완 료 될 때 프로그램 에 데 이 터 를 읽 으 라 고 알려 줍 니 다.
    몇 가지 I/O 모델 의 비교
    앞의 네 가지 모델 의 차 이 는 단계 1 이 다 르 고 단계 2 가 대체적으로 같 으 며 모두 데 이 터 를 커 널 에서 호출 자의 버퍼 로 복사 하 는 것 이다.비동기 I/O 의 두 단 계 는 모두 앞의 네 모델 과 다르다.
    동기 화 I/O 작업 은 I/O 작업 이 끝 날 때 까지 요청 프로 세 스 를 막 습 니 다.비동기 I/O 작업 은 요청 프로 세 스 를 막 지 않 습 니 다.

    흔 한 자바 I/O 모델
    유 닉 스 의 I/O 모델 을 알 아 본 뒤 자바 의 I/O 모델 도 비슷 하 다.
    "I/O 차단"모드
    이전 소켓 섹 션 에 있 는 EchoServer 는 간단 한 I/O 차단 예 입 니 다.서버 가 시 작 된 후에 클 라 이언 트 연결 을 기다 리 고 있 습 니 다.클 라 이언 트 가 서버 에 연결 되면 서버 는 읽 기와 쓰기 데이터 흐름 을 막는다.
    EchoServer 코드:
    
    public class EchoServer {
    public static int DEFAULT_PORT = 7;
    
    public static void main(String[] args) throws IOException {
    
    int port;
    try {
    port = Integer.parseInt(args[0]);
    } catch (RuntimeException ex) {
    port = DEFAULT_PORT;
    }
    try (
    ServerSocket serverSocket =
    new ServerSocket(port);
    Socket clientSocket = serverSocket.accept(); 
    PrintWriter out =
    new PrintWriter(clientSocket.getOutputStream(), true); 
    BufferedReader in = new BufferedReader(
    new InputStreamReader(clientSocket.getInputStream()));
    ) {
    String inputLine;
    while ((inputLine = in.readLine()) != null) {
    out.println(inputLine);
    }
    } catch (IOException e) {
    System.out.println("Exception caught when trying to listen on port "
    + port + " or listening for a connection");
    System.out.println(e.getMessage());
    }
    }
    }
    "I/O+다 중 스 레 드 차단"모드 로 개선
    다 중 스 레 드 를 사용 하여 여러 클 라 이언 트 가 서버 에 접근 하 는 것 을 지원 합 니 다.
    메 인 스 레 드 MultiThreadEchoServer.java
    
    public class MultiThreadEchoServer {
    public static int DEFAULT_PORT = 7;
    public static void main(String[] args) throws IOException {
    int port;
    try {
    port = Integer.parseInt(args[0]);
    } catch (RuntimeException ex) {
    port = DEFAULT_PORT;
    }
    Socket clientSocket = null;
    try (ServerSocket serverSocket = new ServerSocket(port);) {
    while (true) {
    clientSocket = serverSocket.accept();
    // MultiThread
    new Thread(new EchoServerHandler(clientSocket)).start();
    }
    } catch (IOException e) {
    System.out.println(
    "Exception caught when trying to listen on port " + port + " or listening for a connection");
    System.out.println(e.getMessage());
    }
    }
    }
    프로세서 클래스 EchoServerHandler.java
    
    public class EchoServerHandler implements Runnable {
    private Socket clientSocket;
    public EchoServerHandler(Socket clientSocket) {
    this.clientSocket = clientSocket;
    }
    @Override
    public void run() {
    try (PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
    BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));) {
    String inputLine;
    while ((inputLine = in.readLine()) != null) {
    out.println(inputLine);
    }
    } catch (IOException e) {
    System.out.println(e.getMessage());
    }
    }
    }
    문제 가 있 습 니 다:새로운 연결 을 받 을 때마다 스 레 드 를 새로 만들어 야 합 니 다.처리 가 끝 난 후에 스 레 드 를 없 애 는 대가 가 큽 니 다.대량의 짧 은 연결 이 나타 날 때 성능 이 비교적 낮다.
    "I/O+스 레 드 탱크 차단"모드 로 개선
    위의 다 중 스 레 드 모델 에 나타 난 스 레 드 중복 생 성,소각 에 따 른 비용 은 스 레 드 풀 로 최적화 할 수 있 습 니 다.매번 새로운 연결 을 받 은 후에 풀 에서 빈 스 레 드 를 가 져 와 처리 하고 처리 가 끝 난 후에 다시 풀 에 넣 으 며 다시 스 레 드 를 사용 하면 주파수 적 으로 스 레 드 를 만 들 고 없 애 는 비용 을 피 할 수 있 습 니 다.
    주 스 레 드 ThreadPoolEchoServer.java
    
    public class ThreadPoolEchoServer {
    public static int DEFAULT_PORT = 7;
    public static void main(String[] args) throws IOException {
    int port;
    try {
    port = Integer.parseInt(args[0]);
    } catch (RuntimeException ex) {
    port = DEFAULT_PORT;
    }
    ExecutorService threadPool = Executors.newFixedThreadPool(5);
    Socket clientSocket = null;
    try (ServerSocket serverSocket = new ServerSocket(port);) {
    while (true) {
    clientSocket = serverSocket.accept();
    // Thread Pool
    threadPool.submit(new Thread(new EchoServerHandler(clientSocket)));
    }
    } catch (IOException e) {
    System.out.println(
    "Exception caught when trying to listen on port " + port + " or listening for a connection");
    System.out.println(e.getMessage());
    }
    }
    }
    문제 가 존재 합 니 다.대량의 짧 은 연결 장면 에서 성능 이 향상 되 었 습 니 다.매번 스 레 드 를 만 들 거나 없 애 지 않 고 연결 탱크 의 스 레 드 를 다시 사용 하기 때 문 입 니 다.그러나 대량의 긴 연결 장면 에서 스 레 드 가 연결 되 어 장기 적 으로 점용 되 기 때문에 스 레 드 를 자주 만 들 고 없 앨 필요 가 없 기 때문에 장점 이 없다.
    이런 방법 은 중간 규모 의 클 라 이언 트 의 병발 수 에 적용 할 수 있 지만 연결 수가 100,000 이상 이면 성능 이 좋 지 않 을 것 이다.
    '비 차단 I/O'모드 로 개선
    '차단 I/O+스 레 드 탱크'네트워크 모델 은'차단 I/O+다 중 스 레 드'네트워크 모델 보다 성능 이 향상 되 었 지만 이 두 가지 모델 은 모두 공 통 된 문제 가 존재 한다.읽 기와 쓰기 작업 은 모두 동기 적 으로 차단 되 고 큰 병행(대량의 연결 을 지속 하 는 동시에 요청)장면 에 직면 하여 대량의 스 레 드 를 소모 하여 연결 을 유지 해 야 한다.CPU 는 대량의 라인 사이 에서 빈번하게 전환 되 어 성능 손실 이 매우 크다.단일 기기 의 연결 이 1 만 명 을 넘 고 수만 명 에 이 르 면 서버 의 성능 이 급 격 히 떨어진다.
    그러나 NIO 의 Selector 는 이 문 제 를 잘 해결 했다.메 인 스 레 드(하나의 스 레 드 또는 CPU 개수 의 스 레 드)로 모든 연결 을 유지 하고 클 라 이언 트 가 연 결 된 데 이 터 를 관리 하고 읽 으 며 읽 은 데 이 터 를 뒤의 스 레 드 풀 에 맡 겼 다.스 레 드 풀 은 업무 논 리 를 처리 한 후에 결 과 를 메 인 스 레 드 에 응답 을 보 냈 다.소량의 스 레 드 로 대량의 연결 요청 을 처리 할 수 있 습 니 다.
    자바 NIO 는 다음 과 같은 몇 가지 핵심 부분 으로 구성 되 어 있 습 니 다.
  • Channel
  • Buffer
  • Selector
  • Selector 를 사용 하려 면 Selector 에 Channel 을 등록 하고 select()방법 을 사용 해 야 합 니 다.이 방법 은 어떤 등 록 된 통로 가 사건 이 완 료 될 때 까지 계속 막 힐 것 이다.이 방법 이 돌아 오 면 스 레 드 는 이 사건 들 을 처리 할 수 있 습 니 다.사건 의 예 는 새로운 연결,데이터 수신 등 이 있 습 니 다.
    주 스 레 드 NonBloking EchoServer.java
    
    public class NonBlokingEchoServer {
    public static int DEFAULT_PORT = 7;
    public static void main(String[] args) throws IOException {
    int port;
    try {
    port = Integer.parseInt(args[0]);
    } catch (RuntimeException ex) {
    port = DEFAULT_PORT;
    }
    System.out.println("Listening for connections on port " + port);
    ServerSocketChannel serverChannel;
    Selector selector;
    try {
    serverChannel = ServerSocketChannel.open();
    InetSocketAddress address = new InetSocketAddress(port);
    serverChannel.bind(address);
    serverChannel.configureBlocking(false);
    selector = Selector.open();
    serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    } catch (IOException ex) {
    ex.printStackTrace();
    return;
    }
    while (true) {
    try {
    selector.select();
    } catch (IOException ex) {
    ex.printStackTrace();
    break;
    }
    Set<SelectionKey> readyKeys = selector.selectedKeys();
    Iterator<SelectionKey> iterator = readyKeys.iterator();
    while (iterator.hasNext()) {
    SelectionKey key = iterator.next();
    iterator.remove();
    try {
    if (key.isAcceptable()) {
    ServerSocketChannel server = (ServerSocketChannel) key.channel();
    SocketChannel client = server.accept();
    System.out.println("Accepted connection from " + client);
    client.configureBlocking(false);
    SelectionKey clientKey = client.register(selector,
    SelectionKey.OP_WRITE | SelectionKey.OP_READ);
    ByteBuffer buffer = ByteBuffer.allocate(100);
    clientKey.attach(buffer);
    }
    if (key.isReadable()) {
    SocketChannel client = (SocketChannel) key.channel();
    ByteBuffer output = (ByteBuffer) key.attachment();
    client.read(output);
    }
    if (key.isWritable()) {
    SocketChannel client = (SocketChannel) key.channel();
    ByteBuffer output = (ByteBuffer) key.attachment();
    output.flip();
    client.write(output);
    output.compact();
    }
    } catch (IOException ex) {
    key.cancel();
    try {
    key.channel().close();
    } catch (IOException cex) {
    }
    }
    }
    }
    }
    }
    '비동기 I/O'모드 로 개선
    Java SE 7 버 전 이후 비동기 I/O(NIO.2)지원 을 도입 해 고성능 네트워크 애플 리 케 이 션 구축 에 유리 한 장 치 를 제공 했다.
    메 인 스 레 드 AsyncEchoServer.java
    
    public class AsyncEchoServer {
    public static int DEFAULT_PORT = 7;
    public static void main(String[] args) throws IOException {
    int port;
    try {
    port = Integer.parseInt(args[0]);
    } catch (RuntimeException ex) {
    port = DEFAULT_PORT;
    }
    ExecutorService taskExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory());
    // create asynchronous server socket channel bound to the default group
    try (AsynchronousServerSocketChannel asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open()) {
    if (asynchronousServerSocketChannel.isOpen()) {
    // set some options
    asynchronousServerSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 4 * 1024);
    asynchronousServerSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
    // bind the server socket channel to local address
    asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
    // display a waiting message while ... waiting clients
    System.out.println("Waiting for connections ...");
    while (true) {
    Future<AsynchronousSocketChannel> asynchronousSocketChannelFuture = asynchronousServerSocketChannel
    .accept();
    try {
    final AsynchronousSocketChannel asynchronousSocketChannel = asynchronousSocketChannelFuture
    .get();
    Callable<String> worker = new Callable<String>() {
    @Override
    public String call() throws Exception {
    String host = asynchronousSocketChannel.getRemoteAddress().toString();
    System.out.println("Incoming connection from: " + host);
    final ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    // transmitting data
    while (asynchronousSocketChannel.read(buffer).get() != -1) {
    buffer.flip();
    asynchronousSocketChannel.write(buffer).get();
    if (buffer.hasRemaining()) {
    buffer.compact();
    } else {
    buffer.clear();
    }
    }
    asynchronousSocketChannel.close();
    System.out.println(host + " was successfully served!");
    return host;
    }
    };
    taskExecutor.submit(worker);
    } catch (InterruptedException | ExecutionException ex) {
    System.err.println(ex);
    System.err.println("
    Server is shutting down ..."); // this will make the executor accept no new threads // and finish all existing threads in the queue taskExecutor.shutdown(); // wait until all threads are finished while (!taskExecutor.isTerminated()) { } break; } } } else { System.out.println("The asynchronous server-socket channel cannot be opened!"); } } catch (IOException ex) { System.err.println(ex); } } }
    소스 코드
    이 장 에서 예 를 들 어 소스 코드 는https://github.com/waylau/essential-java중 com.waylau.essentialjava.net.echo 가방 에서 찾 을 수 있 습 니 다.
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기