day25 【NIO 和AIO】

1.Selector選擇器

1.多路複用的概念

選擇器Selector是NIO中的重要技術之一。它與SelectableChannel(可通過 Selector 實現多路複用的通道)聯合使用實現了非阻塞的多路複用。使用它可以節省CPU資源,提高程序的運行效率。

"多路複用"是指:多路複用的意思是一個Selector可以監聽多個服務器端口.

  • 服務器端的非多路複用效果

在這裏插入圖片描述

傳統的 IO 流都是阻塞式的。也就是說,當一個線程調用 read() 或 write() 時,該線程被阻塞,直到有一些數據被讀取或寫入,該線程在此期間不能執行其他任務。因此,在完成網絡通信進行 IO 操作時,由於線程會阻塞,所以服務器端必須爲每個客戶端都提供一個獨立的線程進行處理,當服務器端需要處理大量客戶端時,性能急劇下降。

如果不使用“多路複用”,服務器端需要開很多線程處理每個端口的請求。如果在高併發環境下,造成系統性能下降。

  • 服務器端的多路複用效果

在這裏插入圖片描述

Java NIO 是非阻塞模式的。當線程從某通道進行讀寫數據時,若沒有數據可用時,該線程可以進行其他任務。線程通常將非阻塞 IO 的空閒時間用於在其他通道上執行 IO 操作,所以單獨的線程可以管理多個輸入和輸出通道。因此,NIO 可以讓服務器端使用一個或有限幾個線程來同時處理連接到服務器端的所有客戶端。

說明:就是將通道(Channel)註冊在選擇器(Selector)上,然後選擇器負責監聽所有的通道,只要通道中的數據準備就緒好,那麼選擇器就會調用服務器端的一個或者多個線程來執行此通道。如果此時通道沒有準備就緒,那麼服務器端的一個或者多個線程就可以執行其他的任務。

使用了多路複用,只需要一個線程就可以處理多個通道,降低內存佔用率,減少CPU切換時間,在高併發、高頻段業務環境下有非常重要的優勢

2.Selector介紹

​ Selector被稱爲:選擇器,也被稱爲:多路複用器,很多個Channel(通道)可以被註冊到選擇器上,監聽各個Channel上發生的事件,並且能夠根據事件情況決定Channel讀寫。這樣,通過一個線程管理多個Channel,就可以處理大量網絡連接了。

​ 注意:

​ 1.在實際開發中不是線程越多越好,因爲線程之間的切換對操作系統來說代價是很高的,並且每個線程也會佔用一定的系統資源。

​ 2.Selector是一個選擇器,可以用一個線程處理了之前多個線程的事務,這樣就會給系統減輕負擔,提高效率。

3.Selector選擇器的使用

1.創建 Selector :

通過調用 Selector.open() 方法創建一個 Selector。

在這裏插入圖片描述

2.將通道註冊到選擇器上:

​ 爲了能讓Channel和Selector配合使用,我們需要把Channel註冊到Selector上。通過ServerSocketChannel的間接父類SelectableChannel中的註冊方法進行註冊:

 SelectionKey register(Selector sel, int ops) 向給定的選擇器註冊此通道,返回一個選擇鍵。 
     		參數:
     			sel:表示選擇器對象
     			ops:表示選擇器對通道的監聽事件(所得鍵的可用操作集).
     				可以監聽四種不同類型的事件,而且可以使用SelectionKey的四個常量表示:
     					1. 套接字連接--常量:SelectionKey.OP_CONNECT
                        2. 套接字接收--常量:SelectionKey.OP_ACCEPT (ServerSocketChannel							   在註冊時只能使用此項) 
     					3.--常量:SelectionKey.OP_READ
						4.--常量:SelectionKey.OP_WRITE
     

注意:

1.對於ServerSocketChannel在註冊時,只能使用OP_ACCEPT,否則拋出異常。相當於把服務器 ServerSocketChannel的accept()方法交給Selector去管理。

2.註冊的Channel 必須設置成非阻塞模式纔可以,否則非阻塞IO就無法工作,就會報異常。這就意味着我們不能把一個FileChannel註冊到Selector,因爲FileChannel沒有非阻塞模式,但是網絡編程中的ServerSocketChannel是可以的。

其實就是執行代碼:ssc.configureBlocking(false);

		//1.創建服務器對象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //2.綁定端口號
        ssc.bind(new InetSocketAddress(8888));
        //3.設置非阻塞
        ssc.configureBlocking(false);

3.Selector選擇器常見方法

  • int select():選擇一組鍵,其相應的通道已爲 I/O 操作準備就緒。

    • 此方法是Selector幫服務器去等待客戶端的方法

    • 此方法會返回一個int值,表示有幾個客戶端連接了服務器。

    • 此方法在連接到第一個客戶端之前,是阻塞的狀態。

    • 連接到客戶端之後,如果在服務器端沒有處理客戶端請求,那麼select()方法會進入非阻塞狀態。

在這裏插入圖片描述

  • 如果在服務器端已經對連接到的客戶端請求進行處理,select()方法會再次進入阻塞狀態

  • 代碼演示:

    服務器端:

    public class ServerDemo02 {
        public static void main(String[] args) throws IOException {
            //1.創建服務器對象
            ServerSocketChannel ssc = ServerSocketChannel.open();
            //2.綁定端口號
            ssc.bind(new InetSocketAddress(8888));
            //3.設置非阻塞
            ssc.configureBlocking(false);
            //4.要把服務器的事件交給Selector去做
            //創建Selector對象
            Selector selector = Selector.open();
            //5.把Channel註冊到Selector上
            //把服務器的accept()方法交給Selector去管理
            /*
                SelectionKey register(Selector sel, int ops) 向給定的選擇器註冊此通道,返回一個選擇鍵。
             */
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            //循環
            while(true){
                System.out.println(1);
                //Selector現在管理着服務器,在這裏等待客戶端的連接
                selector.select();
                System.out.println(2);
                //處理獲取到的客戶端
                Set<SelectionKey> set = selector.selectedKeys();
                for (SelectionKey key : set) {
    //                SelectableChannel channel = key.channel();
                    ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                    SocketChannel sc = channel.accept();
                }
            }
        }
    }
    

    客戶端:

    public class ClientDemo {
        public static void main(String[] args) throws Exception{
            //創建對象
            SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
            //關流
            sc.close();
        }
    }
    

在這裏插入圖片描述

  • abstract Set selectedKeys() :當客戶端來連接服務器之時,Selector會把被連接的服務器對象放到Set集合中。

服務器代碼演示:

public class ServerDemo02 {
    public static void main(String[] args) throws IOException {
        //1.創建服務器對象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //2.綁定端口號
        ssc.bind(new InetSocketAddress(8888));
        //3.設置非阻塞
        ssc.configureBlocking(false);
        //4.要把服務器的事件交給Selector去做
        //創建Selector對象
        Selector selector = Selector.open();
        //5.把Channel註冊到Selector上
        //把服務器的accept()方法交給Selector去管理
        /*
            SelectionKey register(Selector sel, int ops) 向給定的選擇器註冊此通道,返回一個選擇鍵。
         */
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        //循環
        //獲取存在被連接的服務器對象的集合
        Set<SelectionKey> set = selector.selectedKeys();
        System.out.println("集合中的服務器對象個數:"+set.size());
        System.out.println(1);
        //Selector現在管理着服務器,在這裏等待客戶端的連接
        selector.select();
        System.out.println(2);
        System.out.println("集合中的服務器對象個數:"+set.size());
    }
}

打印結果:

在這裏插入圖片描述

4.使用Selector選擇器接收來自客戶端的數據並打印服務器端

服務器端代碼:

package com.itheima.sh.demo_18;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Set;

public class ServerDemo {
    public static void main(String[] args) throws Exception{
        //創建服務器對象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //綁定端口
        ssc.bind(new InetSocketAddress(8888));
        //設置成非阻塞
        ssc.configureBlocking(false);
        //創建Selector
        Selector selector = Selector.open();
        //註冊
        //把服務器的accept()交給選擇器去管理
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        //讓Selector去接受客戶端
        selector.select();

        //把被連接到的服務器對象放到Set集合中
        Set<SelectionKey> set = selector.selectedKeys();

        //獲取被連接的服務器對象,執行代碼
        /*
            SelectionKey:屬於一個類,表示 SelectableChannel(通道) 和 Selector(選擇器) 之間的註冊關係。
            每次向選擇器註冊通道時就會選擇一個事件(選擇鍵,例如SelectionKey.OP_ACCEPT)。
            方法:abstract  SelectableChannel channel() 返回爲之創建此鍵的通道。
            SelectableChannel屬於ServerSocketChannel的父類
         */
        for (SelectionKey key : set) {
            //獲取通道對象
            SelectableChannel channel = key.channel();
            //向下轉型
            ServerSocketChannel ss = (ServerSocketChannel) channel;
            //獲取客戶端
            SocketChannel s = ss.accept();
            //創建數組
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //讀取數據
            int len = s.read(buffer);
            //打印數據
            //buffer.array() 變爲了普通數組,將普通數組轉換爲字符串,這裏不用切換讀模式
            System.out.println(new String(buffer.array(),0,len));
        }
    }
}

說明:

1.SelectionKey:屬於一個類,表示 SelectableChannel(通道) 和 Selector(選擇器) 之間的註冊關係。每次向選擇器註冊通道時就會選擇一個事件(選擇鍵,例如SelectionKey.OP_ACCEPT)。
2.SelectionKey中的方法:abstract SelectableChannel channel() 返回爲之創建此鍵的通道。
補充: SelectableChannel屬於ServerSocketChannel的父類

客戶端代碼:

package com.itheima.sh.demo_18;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class ClientDemo {
    public static void main(String[] args) throws Exception{
        //創建對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
        //客戶端發數據
        //創建數組
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //數組中添加數據
        buffer.put("你好啊~".getBytes());
        //切換讀模式
        buffer.flip();
        //發出數據
        sc.write(buffer);
        //關流
        sc.close();
    }
}

結果:

在這裏插入圖片描述

4.Selector管理多個ServerSocketChannel

我們已經知道Selector選擇器可以管理多個通道,上面我們只是演示管理一個通道,接下來我們開始演示使用Selector選擇器管理多個通道。

1.Selector選擇器的keys()方法(瞭解)

上述我們已經講解了一個selectedKeys()方法,表示將被連接的服務器對象放到Set集合中。而keys()方法表示將Selector所管理的所有的服務器對象都在這個Set集合裏。

代碼演示如下:

服務器端:

public class ServerDemo {
    public static void main(String[] args) throws Exception {
        //創建服務器對象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //綁定端口
        ssc.bind(new InetSocketAddress(8888));

        //創建服務器對象
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        //綁定端口
        ssc2.bind(new InetSocketAddress(9999));
        //設置成非阻塞
        ssc.configureBlocking(false);
        ssc2.configureBlocking(false);
        //創建Selector
        Selector selector = Selector.open();
        //註冊
        //把服務器的accept()交給選擇器去管理
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);
        //獲取存放所有服務器對象的Set集合
        Set<SelectionKey> set2 = selector.keys();
        System.out.println("set2集合存放服務器的對象個數:" + set2.size());

        //把被連接到的服務器對象放到Set集合中
        Set<SelectionKey> set = selector.selectedKeys();
        System.out.println("set集合存放服務器的對象個數:" + set.size());
        //讓Selector去接受客戶端
        selector.select();
        System.out.println("set2集合存放服務器的對象個數:" + set2.size());
        System.out.println("set集合存放服務器的對象個數:" + set.size());

    }
}

客戶端:

public class ClientDemo {
    public static void main(String[] args) throws Exception{
        //創建對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
        //關流
        sc.close();
    }
}

代碼演示結果:

在這裏插入圖片描述

2.Selector選擇器管理多個通道的問題

​ Selector把被連接的服務器對象放在了一個Set集合中,但是使用完後並沒有刪除。導致在遍歷集合時,遍歷到了已經沒用的對象,出現了異常。

  • 問題演示:

    服務器端:

public class ServerDemo {
    public static void main(String[] args) throws Exception {
        //創建服務器對象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //綁定端口
        ssc.bind(new InetSocketAddress(8888));
        //創建服務器對象
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        //綁定端口
        ssc2.bind(new InetSocketAddress(9999));
        //設置成非阻塞
        ssc.configureBlocking(false);
        ssc2.configureBlocking(false);
        //創建Selector
        Selector selector = Selector.open();
        //註冊
        //把服務器的accept()交給選擇器去管理
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            //把被連接到的服務器對象放到Set集合中
            Set<SelectionKey> set = selector.selectedKeys();
            System.out.println("set集合存放服務器的對象個數:" + set.size());
            //讓Selector去接受客戶端
            selector.select();
            System.out.println("set集合存放服務器的對象個數:" + set.size());
            //遍歷集合set
            for (SelectionKey key : set) {
                //取出服務器對象
                ServerSocketChannel ss = (ServerSocketChannel) key.channel();
                //獲取到客戶端
                SocketChannel s = ss.accept();
                //創建數組
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //讀取數據
                int len = s.read(buffer);
                //打印
                System.out.println(new String(buffer.array(), 0, len));
            }
        }
    }
}

客戶端代碼:

public class ClientDemo {
    public static void main(String[] args) throws Exception{
        //創建對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
        //客戶端發數據
        //創建數組
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //數組中添加數據
        buffer.put("你好啊~".getBytes());
        //切換讀模式
        buffer.flip();
        //發出數據
        sc.write(buffer);
        //關流
        sc.close();
    }
}

1)先運行服務器端:

在這裏插入圖片描述

2)客戶端先連接服務端的端口9999的通道,運行客戶端:

在這裏插入圖片描述

在這裏插入圖片描述
服務端接收到了客戶端的請求數據

3)修改客戶端代碼,變爲連接端口號是8888的通道:

在這裏插入圖片描述
服務器端控制檯:

在這裏插入圖片描述

報異常原因:

在這裏插入圖片描述

問題就出現在獲取selectedKeys()的集合。

第一次的9999連接,selectedKeys()獲取的集合中只有一個SelectionKey對象。

第二次的8888連接,selectedKeys()獲取的集合中有2個SelectionKey對象,一個是連接9999客戶端的,另一個是連接8888客戶端的。而此時應該只處理連接8888客戶端的,所以在上一次處理完9999的數據後,應該將其SelectionKey對象移除。

3.解決Selector選擇器管理多個通道的問題

解決辦法:自己在使用完對象後,從集合中刪除。因爲遍歷刪除元素可能出現併發修改異常,所以要使用迭代器的方法進行刪除。

代碼演示:

public class ServerDemo {
    public static void main(String[] args) throws Exception {
        //創建服務器對象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //綁定端口
        ssc.bind(new InetSocketAddress(8888));

        //創建服務器對象
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        //綁定端口
        ssc2.bind(new InetSocketAddress(9999));
        //設置成非阻塞
        ssc.configureBlocking(false);
        ssc2.configureBlocking(false);
        //創建Selector
        Selector selector = Selector.open();
        //註冊
        //把服務器的accept()交給選擇器去管理
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            //把被連接到的服務器對象放到Set集合中
            Set<SelectionKey> set = selector.selectedKeys();
            System.out.println("set集合存放服務器的對象個數:" + set.size());
            //讓Selector去接受客戶端
            selector.select();
            System.out.println("set集合存放服務器的對象個數:" + set.size());
            //遍歷集合set
//            for (SelectionKey key : set) {
            Iterator<SelectionKey> it = set.iterator();
            while (it.hasNext()) {
                //取出服務器對象
                SelectionKey key = it.next();
                ServerSocketChannel ss = (ServerSocketChannel) key.channel();
                //獲取到客戶端
                SocketChannel s = ss.accept();
                //創建數組
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //讀取數據
                int len = s.read(buffer);
                //打印
                System.out.println(new String(buffer.array(), 0, len));
                //使用完了這個對象就從集合中刪除
                it.remove();
            }

//            }
        }
    }
}

客戶端代碼:

public class ClientDemo {
    public static void main(String[] args) throws Exception{
        //創建對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
        //客戶端發數據
        //創建數組
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //數組中添加數據
        buffer.put("你好啊~".getBytes());
        //切換讀模式
        buffer.flip();
        //發出數據
        sc.write(buffer);
        //關流
        sc.close();
    }
}

效果:

在這裏插入圖片描述

2.NIO2-AIO(異步、非阻塞)

2.1 AIO概述

​ AIO (Asynchronous I/O)AIO 也就是 NIO 2,異步IO的縮寫。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的IO模型。異步 IO 是基於事件和回調機制實現的,也就是應用操作之後會直接返回,不會堵塞在那裏,當後臺處理完成,操作系統會通知相應的線程進行後續的操作。

​ 同步:調用方法之後,必須要得到一個返回值。

​ 異步:調用方法之後,沒有返回值,但是會有回調函數。回調函數指的是滿足條件之後會自動執行的方法(回過 頭來調用的函數,由底層調用)。

​ 阻塞:如果沒有達到方法的目的,就一直停在這裏【等待】。

​ 非阻塞:不管有沒有達到目的,都直接【往下執行】。

在這裏插入圖片描述

2.2 AIO同步寫法【聽下就可以了,用不到】

在演示AIO同步寫法之前,我們先來認識下有關AIO的常見類以及方法:

1.主要在Java.nio.channels包下增加了下面四個異步通道:

  • AsynchronousSocketChannel 異步的客戶端通道
  • **AsynchronousServerSocketChannel **異步的服務器端通道
  • AsynchronousFileChannel 關於本地文件的異步通道
  • AsynchronousDatagramChannel 關於udp的異步通道

2.AIO實現同步的網絡編程:

1)在AIO socket編程中,服務端通道是AsynchronousServerSocketChannel這個類。

  • 獲取服務器端對象:
static AsynchronousServerSocketChannel open() 打開異步服務器套接字通道。 

  • 綁定服務器端口號

    AsynchronousServerSocketChannel bind(SocketAddress local) 將通道的套接字綁定到本地地址,並配置套接字以監聽連接。 
    
  • 偵聽獲取客戶端

    abstract Future<AsynchronousSocketChannel> accept() 接受連接 
        說明:該方法將接收的客戶端存儲到Future<V>接口中,需要使用該接口中的V get() 取出客戶端
    
  • 讀取客戶端的數據:使用客戶端的異步套接字類AsynchronousSocketChanne中的方法:

    abstract Future<Integer> read(ByteBuffer dst) 從該通道讀取到給定緩衝區的字節序列。
        說明:該方法將接收的客戶端的數據存儲到Future<V>接口中,需要使用該接口中的V get() 取出客戶端請求的	數據
    

2)客戶端使用的通道是AsynchronousSocketChannel這個類。

AIO同步寫法代碼實現:

服務器端:

public class Demo{
    public static void main(String[] args) throws Exception {
        //創建對象
        AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
        //綁定端口
        assc.bind(new InetSocketAddress(8888));
        //獲取連接
        //Future裏面放的就是方法的結果
        //********同步********
        System.out.println("準備連接客戶端");
        Future<AsynchronousSocketChannel> future = assc.accept();
        //Future方法需要調用get()方法獲取真正的返回值
        AsynchronousSocketChannel sc = future.get();
        System.out.println("連接上了客戶端");
        //讀取客戶端發來的數據
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //讀取
        //以前返回的是讀取到的個數,真正的個數就在Future裏面放着
        //********同步********
        System.out.println("準備讀取數據");
        Future<Integer> future2 = sc.read(buffer);
        //獲取真正的返回值
        Integer len = future2.get();
        System.out.println("讀取到了數據");
        //打印
        System.out.println(new String(buffer.array(),0,len));
    }
}

客戶端代碼:

public class ClientDemo {
    public static void main(String[] args) throws Exception{
        //創建對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
        Thread.sleep(10000);
        //客戶端發數據
        //創建數組
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //數組中添加數據
        buffer.put("你好啊~".getBytes());
        //切換讀模式
        buffer.flip();
        //發出數據
        sc.write(buffer);
        //關流
        sc.close();
    }
}

2.3 AIO異步非阻塞連接

在AIO編程中,發出一個事件(accept read write等)之後要指定事件處理類(回調函數)。

對於服務器端,我們不再使用之前同步的方法:

abstract Future<AsynchronousSocketChannel> accept() 接受連接 

我們現在使用的是異步的方法:

abstract <A> void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler) 接受連接 
    參數:
    	attachment:表示要附加到I / O操作的對象; 可以是null 
        handler:AIO中的事件處理是CompletionHandler<V,A>接口,這個接口定義瞭如下兩個方法,分別在異步					操作成功和失敗時被回調。
				1void completed(V result, A attachment);當請求到來後,監聽成功,應該做什麼
					參數:result參數就是和客戶端直接建立關聯的通道。
				2void failed(Throwable exc, A attachment); 當服務器代碼出現異常的時候,做什麼					事情

使用代碼演示AIO異步非阻塞連接:

服務器端:

public class ServerDemo{
    public static void main(String[] args) throws IOException {
        //服務器異步的連接方法
        //創建對象
        AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
        //綁定端口
        assc.bind(new InetSocketAddress(8000));
        //【異步非阻塞】方式!!!!!連接客戶端
        System.out.println("11111111111");
        assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            //回調函數,當成功連接了客戶端之後,會自動回來調用這個方法
            public void completed(AsynchronousSocketChannel result, Object attachment) {
                //completed是完成,如果連接成功會自動調用這個方法
                System.out.println("completed");
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                //failed是失敗,如果連接失敗會自動調用這個方法
            }
        });
        System.out.println("22222222222");

        //寫一個死循環讓程序別結束(因爲程序結束了無法演示效果)
        while(true){

        }
    }
}

客戶端代碼:

public class ClientDemo {
    public static void main(String[] args) throws Exception{
        //創建對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000));
        //客戶端發數據
        //創建數組
//        ByteBuffer buffer = ByteBuffer.allocate(1024);
//        //數組中添加數據
//        buffer.put("你好啊~".getBytes());
//        //切換讀模式
//        buffer.flip();
//        //發出數據
//        sc.write(buffer);
        //關流
        sc.close();
    }
}

結果:

在這裏插入圖片描述

2.4 AIO異步非阻塞讀

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;

public class ServerDemo {
    public static void main(String[] args) throws Exception {
        //創建對象
        AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
        //綁定端口
        assc.bind(new InetSocketAddress(8000));
        //異步非阻塞連接!!!!
        //第一個參數是一個附件
        System.out.println(1);
        assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel s, Object attachment) {//attachment就是accept方法的第一個參數null
                System.out.println("attachment = " + attachment);
                //如果連接客戶端成功,應該獲取客戶端發來的數據
                //completed()的第一個參數表示的是Socket對象.
                System.out.println(5);
                //創建數組
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                System.out.println(3);
                //異步非阻塞讀!!!!!
                /*
                    <A> void read​(ByteBuffer dst, A attachment, CompletionHandler<Integer,? super A> handler)
                        參數:
                            dst - 將客戶端的數據存儲到該字節緩衝區中
                            attachment - 要附加到I / O操作的對象; 可以是null
                            handler - 完成處理程序
                 */
                s.read(buffer, null, new CompletionHandler<Integer, Object>() {
                    @Override
                    //讀取成功會自動調用這個方法
                    //completed()方法的第一個參數len是read()讀取到的實際個數
                    public void completed(Integer len, Object attachment) {
                        //打印數據
                        System.out.println(6);
                        System.out.println(new String(buffer.array(), 0, len));
                    }

                    @Override
                    public void failed(Throwable exc, Object attachment) {

                    }
                });
                System.out.println(4);
            }

            @Override
            public void failed(Throwable exc, Object attachment) {

            }
        });

        System.out.println(2);

        //讓程序別結束寫一個死循環
        while (true) {

        }
    }
}

執行順序:
        1
        2
        5
        3
        4
        6

客戶端代碼:

public class ClientDemo {
    public static void main(String[] args) throws Exception{
        //創建對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000));
        //休眠10s
        Thread.sleep(10000);
//        客戶端發數據
//        創建數組
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //數組中添加數據
        buffer.put("你好啊~".getBytes());
        //切換讀模式
        buffer.flip();
        //發出數據
        sc.write(buffer);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章