Java IO 模型及示例

重要概念

同步和異步

概念 含義
同步 IO 時,Java 發起 IO 的線程自己處理 IO 讀寫,因此需要等待 IO 操作完成或者輪詢地去查看 IO 操作是否就緒
異步 IO 時,Java 發起 IO 的線程將 IO 讀寫委託給操作系統,並將數據緩衝區地址和大小傳給操作系統,當 IO 完成時 Java 線程會得到通知

阻塞和非阻塞

概念 含義
阻塞 使用阻塞 IO 時,Java 發起 IO 的線程會一直阻塞到讀寫完成才返回
非阻塞 使用非阻塞 IO 時,不管是否能讀寫,發起 IO 的線程會馬上得到一個返回值,進而繼續執行其他代碼

1. BIO(同步阻塞IO)

1.1 BIO 的處理流程

BIO 的流程通常是服務端啓動一個ServerSocket,然後在客戶端啓動Socket來連接服務端進行通信,默認情況下服務端需要對每個請求建立一個線程進行處理,而客戶端發送請求後線程會等待獲得響應後才繼續執行

在這裏插入圖片描述
Blocking IO的特點就是在IO執行的兩個階段,等待數據和拷貝數據,都被阻塞了。大部分的socket接口都是阻塞型的,而所謂阻塞型接口是指系統調用(一般是IO接口)不返回調用結果並讓當前線程一直阻塞,直到該系統調用獲得結果或者超時出錯時才返回

1.2 使用示例

1.2.1 客戶端

public class Client {
    //默認的端口號
    private static int DEFAULT_SERVER_PORT = 10086;

    private static String DEFAULT_SERVER_IP = "127.0.0.1";

    private static String QUIT_MSG = "q";

    private static Socket socket;

    public static void start() {
        try {
            socket = new Socket(DEFAULT_SERVER_IP, DEFAULT_SERVER_PORT);
            System.out.println("Client: start, port " + DEFAULT_SERVER_PORT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static boolean send(String exp) {
        if (QUIT_MSG.equals(exp)) {
            System.out.println("Client: quit");
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
        System.out.println("Client: send " + exp);
        InputStreamReader inputStreamReader;
        BufferedReader in;
        PrintWriter out;
        try {
            inputStreamReader = new InputStreamReader(socket.getInputStream());
            in = new BufferedReader(inputStreamReader);
            out = new PrintWriter(socket.getOutputStream(), true);

            out.println(exp);
            System.out.println("Client: receive " + in.readLine());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

}

1.2.2 服務端

  1. 服務器類

    public class Server {
    
     //默認的端口號
     private static int DEFAULT_PORT = 10086;
     private static ServerSocket server;
    
     public static void start() {
         start(DEFAULT_PORT);
     }
    
     public synchronized static void start(int port) {
         if (server != null) return;
         new Thread(() -> {
             try {
                 // 通過構造函數創建ServerSocket,如果端口合法且空閒,服務端就監聽成功
                 server = new ServerSocket(port);
                 System.out.println("Server: start, port " + port);
                 // 無限循環監聽客戶端連接,如果沒有客戶端接入,將阻塞在accept操作上
                 while (true) {
                     Socket socket = server.accept();
                     //當有新的客戶端接入時,會執行下面的代碼,創建一個新的線程處理這條 Socket 連接
                     new Thread(new ServerHandler(socket)).start();
                 }
             } catch (IOException e) {
                 e.printStackTrace();
             } finally {
                 //一些必要的清理工作
                 if (server != null) {
                     System.out.println("Server: close");
                     try {
                         server.close();
                     } catch (IOException e) {
                         e.printStackTrace();
                     }
                     server = null;
                 }
             }
         }, "Server").start();
     }
    }
    
  2. 服務器處理者

    public class ServerHandler implements Runnable {
     private Socket socket;
    
     public ServerHandler(Socket socket) {
         this.socket = socket;
     }
    
     @Override
     public void run() {
    
         InputStreamReader inputStreamReader;
         BufferedReader in;
         PrintWriter out;
         try {
             inputStreamReader = new InputStreamReader(socket.getInputStream());
             in = new BufferedReader(inputStreamReader);
             out = new PrintWriter(socket.getOutputStream(), true);
             String exp;
             while (null != (exp = in.readLine())) {
                 // 通過BufferedReader讀取一行,如果已經讀到輸入流尾部或者對端關閉,返回null,退出循環
                 System.out.println("Server: receive " + exp);
                 String result = "Server: send " + exp;
                 out.println(result);
             }
             System.out.println("Server: handler quit");
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             try {
                 if (null != socket)
                     socket.close();
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
    }
    

1.2.3 測試類

  public class Test {

   public static void main(String[] args) throws InterruptedException {

       Server.start();
       //避免客戶端先於服務器啓動前執行代碼
       Thread.sleep(100);

       //運行客戶端
      Client.start();
      while (Client.send(new Scanner(System.in).nextLine()));
   }
  }

2. NIO(同步非阻塞IO)

NIO本身是基於事件驅動思想來完成的,主要解決的是BIO的大併發問題:

在使用同步 I/O 的網絡應用中,如果同時處理多個客戶端請求,就必須使用多線程來處理,也就是將每一個客戶端請求分配給一個線程來單獨處理。這樣做會帶來一個問題,由於每創建一個線程就要爲這個線程分配一定的內存空間,而操作系統本身資源是有限的,這樣就對線程的總數構成了一定的限制。如果客戶端的請求過多,服務端程序可能會因爲不堪重負而拒絕客戶端的請求,甚至服務器可能會因此而癱瘓。
NIO 解決了線程創建過多的問題,不過在 NIO 的處理方式中,當一個請求進來,開啓線程進行處理可能會等待後端應用的資源(JDBC連接等),其實這個線程就被阻塞了,當併發量上來時還是會有和 BIO 一樣的問題

NIO 基於 Reactor,當socket 有流可讀或可寫入 socket 時,操作系統會相應的通知應用程序進行處理,應用再將流讀取到緩衝區或寫入操作系統。 也就是說,這個時候已經不是一個連接就要對應一個處理線程了,而是有效的請求,對應一個線程,當連接沒有數據時,是沒有工作線程來處理的

BIO 與 NIO 比較重要的不同點:

  1. 使用 BIO 的時候往往會引入多線程,每個連接對應一個單獨的線程;而 NIO 線程處理的都是有效連接(數據就緒),且一個線程可以分管處理多個連接上的就緒數據,節省線程資源開銷
  2. BIO 面向流,NIO 面向緩衝區

2.1 NIO 的處理流程

當用戶線程發起 I/O 請求後,會將 socket 連接及關注事件註冊到服務端的selector(多路複用器,os級別線程)上,selector 循環遍歷註冊其上的 socket 連接,看是否有其關注事件就緒,如果連接有數據就緒後,就通知應用程序建立線程進行數據讀寫,也就是實際的讀寫操作是由應用程序完成的,而不是操作系統

在這裏插入圖片描述

2.2 使用示例

2.2.1 客戶端

  1. 客戶端

    public class Client {
    
     private static String DEFAULT_HOST = "127.0.0.1";
    
     private static int DEFAULT_PORT = 10086;
    
     private static String QUIT_MSG = "q";
    
     private static ClientHandler clientHandle;
    
     public static void start() {
         start(DEFAULT_HOST, DEFAULT_PORT);
     }
    
     public static synchronized void start(String ip, int port) {
         if (clientHandle != null) {
             return;
         }
         clientHandle = new ClientHandler(ip, port);
         new Thread(clientHandle, "Client").start();
     }
    
     //向服務器發送消息
     public static boolean sendMsg(String msg) throws Exception {
         if (QUIT_MSG.equals(msg)) {
             System.out.println("Client: exit");
             clientHandle.stop();
             return false;
         }
         clientHandle.sendMsg(msg);
         return true;
     }
    }
    
  2. 客戶端處理者

    public class ClientHandler implements Runnable {
    
     private String host;
     private int port;
     private Selector selector;
     private SocketChannel socketChannel;
     private volatile boolean started;
    
     public ClientHandler(String ip, int port) {
         this.host = ip;
         this.port = port;
         try {
             //創建選擇器
             selector = Selector.open();
             //打開監聽通道
             socketChannel = SocketChannel.open();
             //如果爲 true,則此通道將被置於阻塞模式;如果爲 false,則此通道將被置於非阻塞模式
             socketChannel.configureBlocking(false);
             started = true;
             System.out.println("Client: start, port " + port);
         } catch (IOException e) {
             e.printStackTrace();
             System.exit(1);
         }
     }
    
     public void stop() {
         started = false;
     }
    
     @Override
     public void run() {
         try {
             doConnect();
         } catch (IOException e) {
             e.printStackTrace();
             System.exit(1);
         }
         //循環遍歷selector
         while (started) {
             try {
                 //無論是否有讀寫事件發生,selector每隔1s被喚醒一次
                 selector.select(1000);
                 Set<SelectionKey> keys = selector.selectedKeys();
                 Iterator<SelectionKey> it = keys.iterator();
                 SelectionKey key;
                 while (it.hasNext()) {
                     key = it.next();
                     it.remove();
                     try {
                         handleInput(key);
                     } catch (Exception e) {
                         if (key != null) {
                             key.cancel();
                             if (key.channel() != null) {
                                 key.channel().close();
                             }
                         }
                     }
                 }
             } catch (Exception e) {
                 e.printStackTrace();
                 System.exit(1);
             }
         }
         //selector關閉後會自動釋放裏面管理的資源
         if (selector != null)
             try {
                 selector.close();
             } catch (Exception e) {
                 e.printStackTrace();
             }
     }
    
     private void handleInput(SelectionKey key) throws IOException {
         if (key.isValid()) {
             SocketChannel sc = (SocketChannel) key.channel();
             if (key.isConnectable()) {
                 if (sc.finishConnect()) {
                     System.out.println("Client: connect to server");
                 } else {
                     System.exit(1);
                 }
             }
             //讀消息
             if (key.isReadable()) {
                 //創建ByteBuffer,並開闢一個1M的緩衝區
                 ByteBuffer buffer = ByteBuffer.allocate(1024);
                 //讀取請求碼流,返回讀取到的字節數
                 int readBytes = sc.read(buffer);
                 //讀取到字節,對字節進行編解碼
                 if (readBytes > 0) {
                     //將緩衝區當前的limit設置爲position=0,用於後續對緩衝區的讀取操作
                     buffer.flip();
                     //根據緩衝區可讀字節數創建字節數組
                     byte[] bytes = new byte[buffer.remaining()];
                     //將緩衝區可讀字節數組複製到新建的數組中
                     buffer.get(bytes);
                     String result = new String(bytes, StandardCharsets.UTF_8);
                     System.out.println("Client: receive " + result);
                 } else if (readBytes < 0) {
                     //鏈路已經關閉,釋放資源
                     key.cancel();
                     sc.close();
                 }
             }
         }
     }
    
     //異步發送消息
     private void doWrite(SocketChannel channel, String request) throws IOException {
         //將消息編碼爲字節數組
         byte[] bytes = request.getBytes();
         //根據數組容量創建ByteBuffer
         ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
         //將字節數組複製到緩衝區
         writeBuffer.put(bytes);
         //flip操作
         writeBuffer.flip();
         //發送緩衝區的字節數組
         channel.write(writeBuffer);
     }
    
     private void doConnect() throws IOException {
         if (socketChannel.connect(new InetSocketAddress(host, port))) {
             System.out.println("Client: already connect to Server");
         } else {
             socketChannel.register(selector, SelectionKey.OP_CONNECT);
         }
     }
    
     public void sendMsg(String msg) throws Exception {
         socketChannel.register(selector, SelectionKey.OP_READ);
         doWrite(socketChannel, msg);
     }
    }
    

2.2.2 服務端

  1. 服務器

    public class Server {
     private static int DEFAULT_PORT = 10086;
     private static ServerHandler serverHandle;
    
     public static void start() {
         start(DEFAULT_PORT);
     }
    
     public static synchronized void start(int port) {
         if (serverHandle != null) {
             return;
         }
         serverHandle = new ServerHandler(port);
         new Thread(serverHandle, "Server").start();
     }
    }
    
  2. 服務端處理者

    public class ServerHandler implements Runnable {
     private Selector selector;
     private ServerSocketChannel serverChannel;
     private volatile boolean started;
    
     /**
      * 構造方法
      *
      * @param port 指定要監聽的端口號
      */
     public ServerHandler (int port) {
         try {
             //創建選擇器
             selector = Selector.open();
             //打開監聽通道
             serverChannel = ServerSocketChannel.open();
             //如果爲 true,則此通道將被置於阻塞模式;如果爲 false,則此通道將被置於非阻塞模式
             serverChannel.configureBlocking(false);
             //綁定端口 backlog設爲1024
             serverChannel.socket().bind(new InetSocketAddress(port), 1024);
             //監聽客戶端連接請求
             serverChannel.register(selector, SelectionKey.OP_ACCEPT);
             //標記服務器已開啓
             started = true;
             System.out.println("Server: start, port " + port);
         } catch (IOException e) {
             e.printStackTrace();
             System.exit(1);
         }
     }
    
     public void stop() {
         started = false;
     }
    
     @Override
     public void run() {
         //循環遍歷selector
         while (started) {
             try {
                 //無論是否有讀寫事件發生,selector每隔1s被喚醒一次
                 selector.select(1000);
                 Set<SelectionKey> keys = selector.selectedKeys();
                 Iterator<SelectionKey> it = keys.iterator();
                 SelectionKey key;
                 while (it.hasNext()) {
                     key = it.next();
                     it.remove();
                     try {
                         handleInput(key);
                     } catch (Exception e) {
                         if (key != null) {
                             key.cancel();
                             if (key.channel() != null) {
                                 key.channel().close();
                             }
                         }
                     }
                 }
             } catch (Throwable t) {
                 t.printStackTrace();
             }
         }
         //selector關閉後會自動釋放裏面管理的資源
         if (selector != null)
             try {
                 selector.close();
             } catch (Exception e) {
                 e.printStackTrace();
             }
     }
    
     private void handleInput(SelectionKey key) throws IOException {
         if (key.isValid()) {
             //處理新接入的請求消息
             if (key.isAcceptable()) {
                 ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                 //通過ServerSocketChannel的accept創建SocketChannel實例
                 //完成該操作意味着完成TCP三次握手,TCP物理鏈路正式建立
                 SocketChannel sc = ssc.accept();
                 //設置爲非阻塞的
                 sc.configureBlocking(false);
                 //註冊處理讀就緒事件
                 sc.register(selector, SelectionKey.OP_READ);
             }
             // 讀就緒事件觸發讀消息
             if (key.isReadable()) {
                 SocketChannel sc = (SocketChannel) key.channel();
                 //創建ByteBuffer,並開闢一個1M的緩衝區
                 ByteBuffer buffer = ByteBuffer.allocate(1024);
                 //讀取請求碼流,返回讀取到的字節數
                 int readBytes = sc.read(buffer);
                 //讀取到字節,對字節進行編解碼
                 if (readBytes > 0) {
                     //將緩衝區當前的limit設置爲position=0,用於後續對緩衝區的讀取操作
                     buffer.flip();
                     //根據緩衝區可讀字節數創建字節數組
                     byte[] bytes = new byte[buffer.remaining()];
                     //將緩衝區可讀字節數組複製到新建的數組中
                     buffer.get(bytes);
                     String exp = new String(bytes, StandardCharsets.UTF_8);
                     System.out.println("Server: receive " + exp);
                     //處理數據
                     String result;
                     try {
                         result = "Server: send " + exp;
                     } catch (Exception e) {
                         result = "Error: " + e.getMessage();
                     }
                     //發送應答消息
                     doWrite(sc, result);
                 } else if (readBytes < 0) {
                     //鏈路已經關閉,釋放資源
                     key.cancel();
                     sc.close();
                 }
             }
         }
     }
    
     //異步發送應答消息
     private void doWrite(SocketChannel channel, String response) throws IOException {
         //將消息編碼爲字節數組
         byte[] bytes = response.getBytes();
         //根據數組容量創建ByteBuffer
         ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
         //將字節數組複製到緩衝區
         writeBuffer.put(bytes);
         //flip操作
         writeBuffer.flip();
         //發送緩衝區的字節數組
         channel.write(writeBuffer);
     }
    }
    

2.2.3 測試類

public class Test {

    public static void main(String[] args) throws InterruptedException {

        Server.start();
        //避免客戶端先於服務器啓動前執行代碼
        Thread.sleep(100);

        //運行客戶端
       Client.start();
       while (Client.send(new Scanner(System.in).nextLine()));

    }
}

3. AIO(異步非阻塞IO)

3.1 AIO 的處理流程

NIO不同,AIO 基於 Proactor,進行讀寫操作時,只須直接調用 APIread/write方法。這兩種方法都是異步的,對於讀操作而言,當有流可讀取時,操作系統會將可讀的流傳入read方法的緩衝區,並通知應用程序;對於寫操作而言,當操作系統將write方法傳遞的流寫入完畢時,也會主動通知應用程序。 即可以理解爲,read/write方法都是異步的,完成後會主動調用數據接收方的回調函數

在這裏插入圖片描述

3.2 使用示例

3.2.1 客戶端

  1. 客戶端

    public class Client {
     private static String DEFAULT_HOST = "127.0.0.1";
    
     private static int DEFAULT_PORT = 10086;
    
     private static String QUIT_MSG = "q";
    
     private static AsyncClientHandler clientHandle;
    
     public static void start() {
         start(DEFAULT_HOST, DEFAULT_PORT);
     }
    
     public static synchronized void start(String ip, int port) {
         if (clientHandle != null) {
             return;
         }
         clientHandle = new AsyncClientHandler(ip, port);
         new Thread(clientHandle, "Client").start();
     }
    
     //向服務器發送消息
     public static boolean sendMsg(String msg) {
         if (QUIT_MSG.equals(msg)) {
             clientHandle.stop();
             return false;
         }
         clientHandle.sendMsg(msg);
         return true;
     }
    }
    
  2. 客戶端處理者

    public class AsyncClientHandler implements CompletionHandler<Void, AsyncClientHandler>, Runnable {
     private AsynchronousSocketChannel clientChannel;
     private String host;
     private int port;
     private CountDownLatch latch;
    
     public AsyncClientHandler(String host, int port) {
         this.host = host;
         this.port = port;
         try {
             //創建異步的客戶端通道
             clientChannel = AsynchronousSocketChannel.open();
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
    
     @Override
     public void run() {
         //創建CountDownLatch等待
         latch = new CountDownLatch(1);
         //發起異步連接操作,回調接口就是這個類本身,如果連接成功會回調completed方法
         clientChannel.connect(new InetSocketAddress(host, port), this, this);
         try {
             latch.await();
         } catch (InterruptedException e1) {
             e1.printStackTrace();
         }
         try {
             clientChannel.close();
             System.out.println("Client: close");
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
    
     public void stop() {
         latch.countDown();
     }
    
     //連接服務器成功
     //意味着TCP三次握手完成
     @Override
     public void completed(Void result, AsyncClientHandler attachment) {
         System.out.println("Client: connect to server");
     }
    
     //連接服務器失敗
     @Override
     public void failed(Throwable exc, AsyncClientHandler attachment) {
         System.err.println("Client: connect to server fail");
         exc.printStackTrace();
         try {
             clientChannel.close();
             latch.countDown();
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
    
     //向服務器發送消息
     public void sendMsg(String msg) {
         byte[] req = msg.getBytes();
         ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
         writeBuffer.put(req);
         writeBuffer.flip();
         //異步寫
         clientChannel.write(writeBuffer, writeBuffer, new ClientWriteHandler(clientChannel, latch));
     }
    }
    
  3. 客戶端寫處理

    public class ClientWriteHandler implements CompletionHandler<Integer, ByteBuffer> {
     private AsynchronousSocketChannel clientChannel;
     private CountDownLatch latch;
    
     public ClientWriteHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch) {
         this.clientChannel = clientChannel;
         this.latch = latch;
     }
    
     @Override
     public void completed(Integer result, ByteBuffer buffer) {
         //完成全部數據的寫入
         if (buffer.hasRemaining()) {
             clientChannel.write(buffer, buffer, this);
         } else {
             //讀取數據
             ByteBuffer readBuffer = ByteBuffer.allocate(1024);
             clientChannel.read(readBuffer, readBuffer, new ClientReadHandler(clientChannel, latch));
         }
     }
    
     @Override
     public void failed(Throwable exc, ByteBuffer attachment) {
         System.err.println("Client: send fail");
         try {
             clientChannel.close();
         } catch (IOException e) {
             e.printStackTrace();
         } finally {
             latch.countDown();
         }
     }
    }
    
  4. 客戶端讀處理

    public class ClientReadHandler implements CompletionHandler<Integer, ByteBuffer> {
     private AsynchronousSocketChannel clientChannel;
     private CountDownLatch latch;
    
     public ClientReadHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch) {
         this.clientChannel = clientChannel;
         this.latch = latch;
     }
    
     @Override
     public void completed(Integer result, ByteBuffer buffer) {
         buffer.flip();
         byte[] bytes = new byte[buffer.remaining()];
         buffer.get(bytes);
         String body;
         body = new String(bytes, StandardCharsets.UTF_8);
         System.out.println("Client: receive " + body);
     }
    
     @Override
     public void failed(Throwable exc, ByteBuffer attachment) {
         System.err.println("Client: receive fail");
         try {
             clientChannel.close();
         } catch (IOException e) {
             e.printStackTrace();
         } finally {
             latch.countDown();
         }
     }
    }
    

3.2.2 服務端

  1. 服務器

    public class Server {
    
     private static int DEFAULT_PORT = 10086;
     private static AsyncServerHandler SERVER_HANDLER;
    
     public static void start() {
         start(DEFAULT_PORT);
     }
    
     public static synchronized void start(int port) {
         if (SERVER_HANDLER != null) {
             System.out.println("Server: already start");
             return;
         }
         SERVER_HANDLER = new AsyncServerHandler(port);
         new Thread(SERVER_HANDLER, "Server").start();
     }
    }
    
  2. 服務端處理者

    public class AsyncServerHandler implements Runnable {
     public CountDownLatch latch;
     public AsynchronousServerSocketChannel serverSocketChannel;
    
     public AsyncServerHandler(int port) {
         try {
             //創建服務端通道
             serverSocketChannel = AsynchronousServerSocketChannel.open();
             //綁定端口
             serverSocketChannel.bind(new InetSocketAddress(port));
             System.out.println("Server: start, port " + port);
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
    
     @Override
     public void run() {
         // CountDownLatch 初始化
         // 作用:在完成一組正在執行的操作之前,允許當前的線程一直阻塞,防止服務端執行完成後退出, 也可以使用 while(true) + sleep
         latch = new CountDownLatch(1);
         //用於接收客戶端的連接
         serverSocketChannel.accept(this, new AcceptHandler());
         try {
             latch.await();
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
    }
    
  3. 服務端接受連接處理

    public class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncServerHandler> {
    
     private static int CLIENT_COUNT = 0;
    
     @Override
     public void completed(AsynchronousSocketChannel channel, AsyncServerHandler attachment) {
         //繼續接受其他客戶端的請求
         CLIENT_COUNT++;
         System.out.println("Server: client num " + CLIENT_COUNT);
         attachment.serverSocketChannel.accept(attachment, this);
         //創建新的Buffer
         ByteBuffer buffer = ByteBuffer.allocate(1024);
         //異步讀  第三個參數爲接收消息回調的業務Handler
         channel.read(buffer, buffer, new ReadHandler(channel));
     }
    
     @Override
     public void failed(Throwable exc, AsyncServerHandler serverHandler) {
         exc.printStackTrace();
         serverHandler.latch.countDown();
     }
    }
    
  4. 服務端讀處理

    public class ReadHandler implements CompletionHandler<Integer, ByteBuffer> {
     //用於讀取半包消息和發送應答
     private AsynchronousSocketChannel channel;
    
     public ReadHandler(AsynchronousSocketChannel channel) {
         this.channel = channel;
    
     }
    
     //讀取到消息後的處理
     @Override
     public void completed(Integer result, ByteBuffer buffer) {
         //flip操作
         buffer.flip();
         byte[] message = new byte[buffer.remaining()];
         buffer.get(message);
         String expr = new String(message, StandardCharsets.UTF_8);
         System.out.println("Server: receive " + expr);
         String resultMsg;
         try {
             resultMsg = "Server: send " + expr;
         } catch (Exception e) {
             resultMsg = "Error:" + e.getMessage();
         }
         //向客戶端發送消息
         doWrite(resultMsg);
     }
    
     //發送消息
     private void doWrite(String result) {
         byte[] bytes = result.getBytes();
         ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
         writeBuffer.put(bytes);
         writeBuffer.flip();
         //異步寫數據 參數與前面的read一樣
         channel.write(writeBuffer, writeBuffer, new WriteHandler(channel));
     }
    
     @Override
     public void failed(Throwable exc, ByteBuffer attachment) {
         try {
             this.channel.close();
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
    }
    
  5. 服務端寫處理

    public class WriteHandler implements CompletionHandler<Integer, ByteBuffer> {
    
     private AsynchronousSocketChannel channel;
    
     public WriteHandler(AsynchronousSocketChannel channel) {
         this.channel = channel;
     }
    
     @Override
     public void completed(Integer result, ByteBuffer buffer) {
         //如果沒有發送完,就繼續發送直到完成
         if (buffer.hasRemaining()) {
             channel.write(buffer, buffer, this);
         } else {
             //發送完畢,準備讀取數據
             ByteBuffer readBuffer = ByteBuffer.allocate(1024);
             //異步讀  第三個參數爲接收消息回調的業務 Handler
             channel.read(readBuffer, readBuffer, new ReadHandler(channel));
         }
     }
    
     @Override
     public void failed(Throwable exc, ByteBuffer attachment) {
         try {
             channel.close();
         } catch (IOException ignored) {
         }
     }
    }
    

3.2.3 測試類

public class Test {

    public static void main(String[] args) throws Exception {
        //運行服務器
        Server.start();
        //避免客戶端先於服務器啓動前執行代碼
        Thread.sleep(100);
        //運行客戶端
        Client.start();
        System.out.println("please input:");
        Scanner scanner = new Scanner(System.in);
        while (Client.sendMsg(scanner.nextLine()));
        scanner.close();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章