day25【MappedByteBuffer、網絡編程、Selector選擇器、NIO2-AIO(異步、非阻塞)】課上

1.使用MappedByteBuffer複製超過2G的文件(理解)

1.圖解

在這裏插入圖片描述

2.代碼演示

package com.itheima.sh.filechannel_01;

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/*
    將D:\上課視頻.zip複製到F:\上課視頻.zip
 */
public class FileChannelDemo01 {
    public static void main(String[] args) throws Exception{
        //1.創建訪問隨機文件的類的對象
        RandomAccessFile f1 = new RandomAccessFile("D:\\上課視頻.zip", "r");//只讀
        RandomAccessFile f2 = new RandomAccessFile("F:\\上課視頻.zip", "rw");//讀寫

        //2.獲取通道
        FileChannel c1 = f1.getChannel();
        FileChannel c2 = f2.getChannel();

        //3.獲取文件大小
        long size = c1.size();
//        System.out.println("size = " + size);//2744459212
        //4.定義變量保存每次複製的文件的大小
        long everySize = 1024 * 1024 * 500;//500M  1024字節等於1KB   1024 * 1024  等於1M

        //5.定義變量保存複製文件的次數
        long count = (size % everySize == 0) ? size / everySize : size / everySize + 1;
        //6.使用循環控制每次複製的代碼
        for (long i = 0; i < count; i++) {//i等於0表示第一次  1 表示第二次
            //7.定義變量保存每次開始複製的起始索引
            long start = everySize * i;
            //8.定義變量保存每次複製的文件的真正大小
            long trueSize = (size - start > everySize) ? everySize : size - start;
            /*
                abstract  MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
                    參數:
                        mode:表示讀寫模式
                        position:表示複製文件的起始位置
                        size:表示每次文件的大小
             */
            //9.創建緩衝區
            MappedByteBuffer m1 = c1.map(FileChannel.MapMode.READ_ONLY, start, trueSize);//只讀
            MappedByteBuffer m2 = c2.map(FileChannel.MapMode.READ_WRITE, start, trueSize);//讀寫
            //10.讀寫數據
            for (long l = 0; l < trueSize; l++) {
                //獲取
                byte b = m1.get();
                //存儲
                m2.put(b);
            }
        }
        //釋放資源
        c2.close();
        c1.close();
        f2.close();
        f1.close();

    }
}

2.網絡編程收發信息 (掌握)

1.客戶端

package com.itheima.sh.net_channel_02;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/*
    nio的客戶端:
        1.在nio中使用SocketChannel表示客戶端套接字的通道
        2.獲取SocketChannel對象方法:
            1)簡單方式:
                使用SocketChannel類中的靜態方法:
                    static SocketChannel open(SocketAddress remote)  參數:remote屬於SocketAddress類型,屬於抽象類,我們使用子類創建對象
                                            InetSocketAddress 類,構造方法:InetSocketAddress(String hostname, int port)
                                                                                參數:
                                                                                    hostname:表示連接的服務器ip地址
                                                                                    port:表示連接的服務器的端口號
          2)麻煩方式:
             使用SocketChannel類中的靜態方法:
                 static SocketChannel open();
              使用SocketChannel類中的非靜態方法:
              boolean connect(SocketAddress remote)
                             參數:remote屬於SocketAddress類型,屬於抽象類,我們使用子類創建對象
                                            InetSocketAddress 類,構造方法:InetSocketAddress(String hostname, int port)
                                                                                參數:
                                                                                    hostname:表示連接的服務器ip地址
                                                                                    port:表示連接的服務器的端口號
      3.寫方法:
          int write(ByteBuffer src) 將字節序列從給定的緩衝區中寫入此通道
          abstract  int read(ByteBuffer dst) 將字節序列從此通道中讀入給定的緩衝區。
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.創建客戶端通道對象連接服務器  static SocketChannel open(SocketAddress remote)
        //InetSocketAddress(String hostname, int port)
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
        //2.向服務器寫數據
        //2.1創建緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //2.2向緩衝區中添加數據
        buffer.put("hello,我來了".getBytes());
        //2.3切換讀模式 limit拿到position位置 position拿到0位置 清除mark
        buffer.flip();
        // int write(ByteBuffer src) 將字節序列從給定的緩衝區中寫入此通道
        sc.write(buffer);
        //關閉流
        sc.close();
    }
}

小結:

1.創建客戶端對象:

使用SocketChannel類中的靜態方法:
   static SocketChannel open(SocketAddress remote)  參數:remote屬於SocketAddress類型,屬於抽象類,我們使用子類創建對象
         InetSocketAddress 類,構造方法:InetSocketAddress(String hostname, int port)
                參數:
    				hostname:表示連接的服務器ip地址
                     port:表示連接的服務器的端口號
  1. int write(ByteBuffer src) 將字節序列從給定的緩衝區中寫入此通道
  2. abstract int read(ByteBuffer dst) 將字節序列從此通道中讀入給定的緩衝區。

2.服務器端

package com.itheima.sh.net_channel_02;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/*
    服務器端:
    1.在nio中使用ServerSocketChannel表示服務器端套接字的通道
    2.創建ServerSocketChannel對象:
        1)使用ServerSocketChannel類的方法:static ServerSocketChannel open()
        2)使用ServerSocketChannel對象調用父類的綁定端口號方法:
            ServerSocketChannel bind(SocketAddress local) 將通道的套接字綁定到本地地址,並配置套接字以
                    參數:SocketAddress屬於抽象類,使用子類InetSocketAddress(int port) 指定服務器的端口號
   3.使用服務器的對象調用方法獲取客戶端通道對象:
    abstract  SocketChannel accept()  接受到此通道套接字的連接。
   4.使用偵聽的客戶端對象調用客戶端中的讀取方法:
     int read(ByteBuffer dst)
 */
public class ServerDemo02 {
    public static void main(String[] args) throws IOException {
        //1.創建ServerSocketChannel對象
        //1.1 使用ServerSocketChannel類的方法:static ServerSocketChannel open()
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //1.2 ServerSocketChannel bind(SocketAddress local)
        //使用子類InetSocketAddress(int port) 指定服務器的端口號
        ssc.bind(new InetSocketAddress(9999));
        //2.使用服務器的對象調用方法獲取客戶端通道對象:abstract  SocketChannel accept()  接受到此通道套接字的連接。
        System.out.println("1111");
        //阻塞到這裏了,等待客戶端訪問。
        SocketChannel sc = ssc.accept();
        System.out.println("2222");
        //3.讀取數據 int read(ByteBuffer dst)
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = sc.read(buffer);//讀取個數
        //4.將buffer轉換爲普通數組
        byte[] arr = buffer.array();
        //5.輸出客戶端數據
        //這裏操作的是普通數組arr,不是緩衝區,所以不用切換讀模式
        System.out.println(new String(arr,0,len));
        //6.釋放資源
        sc.close();
        ssc.close();
    }
}

小結:

1.創建服務器套接字對象:

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(9999));

2.abstract SocketChannel accept() 接受到此通道套接字的連接。

3.解決上述accept阻塞的問題

package com.itheima.sh.net_channel_02;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;


public class ClientDemo03 {
    public static void main(String[] args) throws IOException {
        //1.創建客戶端通道對象連接服務器  static SocketChannel open(SocketAddress remote)
        //InetSocketAddress(String hostname, int port)
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 12306));
        //關閉流
        sc.close();
    }
}


package com.itheima.sh.net_channel_02;

import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/*
    解決accept方法阻塞問題:

 */
public class ServerDemo03 {
    public static void main(String[] args) throws Exception{
        //1.創建ServerSocketChannel對象
        //1.1 使用ServerSocketChannel類的方法:static ServerSocketChannel open()
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //1.2 ServerSocketChannel bind(SocketAddress local)
        //使用子類InetSocketAddress(int port) 指定服務器的端口號
        ssc.bind(new InetSocketAddress(12306));

        //2.設置非阻塞模式:
        /*
            abstract  SelectableChannel configureBlocking(boolean block) 調整此通道的阻塞模式。
                參數是false表示非阻塞
         */
        ssc.configureBlocking(false);

        //3.獲取客戶端
        //由於這裏設置了非阻塞模式,那麼如果沒有客戶端,該accept方法返回null
//        System.out.println("111111");
//        SocketChannel sc = ssc.accept();
//        System.out.println("222222"+sc);
        //使用死循環
        while(true){
            SocketChannel sc = ssc.accept();
            //判斷sc是否等於null
            if(sc == null){
                //說明沒有客戶端
                System.out.println("沒有客戶端連接,玩會");
                Thread.sleep(2000);
            }else{
                //說明有客戶端
                System.out.println("連接上了客戶端");
                break;
            }
        }
    }
}

小結:

我們可以使用ServerSocketChannel的父類中的方法設置爲服務器爲非阻塞方式:

 abstract  SelectableChannel configureBlocking(boolean block) 調整此通道的阻塞模式。
         參數是false表示非阻塞

3.Selector選擇器(掌握)

1.選擇器介紹

nio三大組件:

1.buffer 緩衝區 負責存儲數據

2.channel 通道 負責建立連接

3.selector 選擇器 主要負責一個選擇器可以監聽多個通道 是非阻塞的核心

阻塞模式:

在這裏插入圖片描述

非阻塞模式:

在這裏插入圖片描述

小結:

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

2.多路複用:一個selector可以監聽多個服務器端口號

2.Selector選擇器的使用

package com.itheima.sh.selector_03;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;

/*
    客戶端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.創建客戶端通道對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 12306));

        //釋放資源
        sc.close();

    }
}

package com.itheima.sh.selector_03;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

/*
    選擇器的使用:
        1.選擇器使用Selector表示,屬於抽象類
        2.獲取選擇器:
            static Selector open() 打開一個選擇器。
        3.將連接通道註冊到選擇器:
            使用服務器端的套接字通道ServerSocketChannel的父類SelectableChannel中的註冊方法:
                 SelectionKey register(Selector sel, int ops)  向給定的選擇器註冊此通道,返回一個選擇鍵。
                    參數:
                        sel:表示被註冊的選擇器
                        ops:所得鍵的可用操作集 所得鍵就是SelectionKey,該類表示 SelectableChannel(通道) 在 Selector(選擇器) 中的註冊的標記
                            SelectionKey選擇鍵的成員變量:
                                static int OP_ACCEPT 用於套接字【接受】操作的操作集位。
                                        說明:我們使用服務器套接字ServerSocketChannel和客戶端建立連接,這裏必須指定 OP_ACCEPT,否則就會報錯
                                static int OP_CONNECT 用於套接字【連接】操作的操作集位。
                                static int OP_READ 用於【讀取】操作的操作集位。
                                static int OP_WRITE 用於【寫入】操作的操作集位。
       4.註冊到選擇器上的channel必須是非阻塞模式,通過ServerSocketChannel的父類SelectableChannel方法:
                abstract  SelectableChannel configureBlocking(boolean block)  false表示非阻塞
       5.方法:abstract  int select() 選擇一組鍵,其相應的通道已爲 I/O 操作準備就緒
                說明:等待客戶端訪問,如果沒有客戶端訪問,那麼此時一直等待客戶端,只要有客戶端訪問,如果
                服務器不做處理,那麼就不會等待了。
                返回值表示獲取到的客戶端數量
 */
public class ServerDemo01 {
    public static void main(String[] args) throws Exception {
        //1.創建服務器的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //2.綁定端口號
        ssc.bind(new InetSocketAddress(12306));

        //3.設置爲非阻塞模式 abstract  SelectableChannel configureBlocking(boolean block)  false表示非阻塞
        ssc.configureBlocking(false);
        //4.獲取選擇器
        Selector selector = Selector.open();

        //5.將通道註冊到選擇器上
        /*
             使用服務器端的套接字通道ServerSocketChannel的父類SelectableChannel中的註冊方法:
             SelectionKey register(Selector sel, int ops)  向給定的選擇器註冊此通道,返回一個選擇鍵。

                    註冊方法第二個參數:
                            ops:所得鍵的可用操作集 所得鍵就是SelectionKey,該類表示 SelectableChannel(通道) 在
                            Selector(選擇器) 中的註冊的標記
                            SelectionKey選擇鍵的成員變量:
                                static int OP_ACCEPT 用於套接字【接受】操作的操作集位。
         */
        ssc.register(selector, SelectionKey.OP_ACCEPT);//表示將通道ssc註冊到選擇器上,就是將偵聽並獲取客戶端的accept()方法交給了選擇器
        /*
            方法:abstract  int select() 選擇一組鍵,其相應的通道已爲 I/O 操作準備就緒
                說明:等待客戶端訪問,如果沒有客戶端訪問,那麼此時一直等待客戶端,只要有客戶端訪問,如果
                服務器不做處理,那麼就不會等待了。
                返回值表示獲取到的客戶端數量
         */
       /* System.out.println("1");
        //6.使用選擇器對象調用選擇器類的方法abstract  int select() 等待客戶端
        int count = selector.select();
        System.out.println("2");
        System.out.println("count = " + count);*/

       while(true){
           System.out.println("1");
           //6.使用選擇器對象調用選擇器類的方法abstract  int select() 等待客戶端
           /*
                只要有客戶端訪問,如果服務器不做處理,那麼就不會等待了。
                如果在服務器中處理了客戶端,那麼select()方法就會等待下個客戶端訪問
            */
           int count = selector.select();
           //休眠
           Thread.sleep(2000);
           System.out.println("2");
           System.out.println("count = " + count);
           /*
                處理客戶端
            */
           Set<SelectionKey> keys = selector.selectedKeys();
           for (SelectionKey key : keys) {
               ServerSocketChannel ssc2 = (ServerSocketChannel) key.channel();
               SocketChannel sc = ssc2.accept();
           }
       }


    }
}

小結:

選擇器使用步驟:

1.創建服務器通道:

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(12306));

2.設置爲非阻塞

ssc.configureBlocking(false);

3.創建選擇器

Selector selector = Selector.open();

4.將通道註冊到選擇器上

ssc.register(selector, SelectionKey.OP_ACCEPT);

5.使用選擇器操作通道

3.Selector選擇器方法

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

說明:

1.SelectionKey表示 SelectableChannel(通道) 在 Selector (選擇器)中的註冊的關係。 是一個類

  • Set keys() 將服務器的所用對象放到set集合中

  • 代碼演示:

    package com.itheima.sh.selector_04;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.util.Set;
    
    /*
        1.Selector選擇器中的方法:
            1)abstract  Set<SelectionKey> selectedKeys()  :當客戶端來連接服務器之時,Selector會把被連接的**服務器對象**放到Set集合中。
            2)Set<SelectionKey> keys() 將服務器的所用對象放到set集合中
    
     */
    public class ServerDemo01 {
        public static void main(String[] args) throws IOException {
            //1.創建服務器對象
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.bind(new InetSocketAddress(9999));
    
            ServerSocketChannel ssc2 = ServerSocketChannel.open();
            ssc2.bind(new InetSocketAddress(8888));
    
    
            //2.設置爲非阻塞模式
            ssc.configureBlocking(false);
            ssc2.configureBlocking(false);
            //3.獲取選擇器
            Selector selector = Selector.open();
            //4.將通道ssc註冊到選擇器上
            //多個通道註冊到同一個選擇器上
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            ssc2.register(selector, SelectionKey.OP_ACCEPT);
            //5.abstract  Set<SelectionKey> selectedKeys()  :當客戶端來連接服務器之時,Selector會把被連接的**服務器對象**放到Set集合中。
            Set<SelectionKey> set1 = selector.selectedKeys();
            //set1集合中的服務器對象個數:0
            System.out.println("set1集合中的服務器對象個數:"+set1.size());
    
            //獲取所有的服務器對象
            Set<SelectionKey> set2 = selector.keys();
            //set2集合中的服務器對象個數:2
            System.out.println("set2集合中的服務器對象個數:"+set2.size());
            //6.調用select方法連接客戶端
            selector.select();
            //輸出
            //set集合中的服務器對象個數:1
            System.out.println("set集合中的服務器對象個數:"+set1.size());
            //set2集合中的服務器對象個數:2
            System.out.println("set2集合中的服務器對象個數:"+set2.size());
    
    
        }
    }
    
    

    小結:

    1.Set selectedKeys() 將被連接的服務器對象放到set集合中

    2.Set keys() 將服務器的所用對象放到set集合中

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

客戶端:

package com.itheima.sh.selector_05;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/*
    客戶端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.創建客戶端對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 7777));
        //2.創建緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //3.添加數據
        buffer.put("大家好,我是nio,我來了,我難嗎".getBytes());
        //4.切換讀模式
        buffer.flip();
        //5.寫數據
        sc.write(buffer);
        //6.釋放資源
        sc.close();
    }
}

服務器端:

package com.itheima.sh.selector_05;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

/*
    服務器端
    使用Selector選擇器接收來自客戶端的數據並打印服務器端
 */
public class ServerDemo01 {
    public static void main(String[] args) throws IOException {
        //1.創建服務器對象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(7777));

        //2.設置非阻塞
        ssc.configureBlocking(false);

        //3.創建選擇器
        Selector selector = Selector.open();

        //4.將通道註冊到選擇其實上
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        //5.連接客戶端
        selector.select();
        //6.獲取被連接的服務器對象放到set集合
        Set<SelectionKey> set1 = selector.selectedKeys();
        System.out.println("被連接的服務器對象個數:"+set1.size());

        //7.遍歷集合
        for (SelectionKey key : set1) {//key屬於SelectionKey類型表示通道ssc和選擇器之間的關係
            //8.abstract  SelectableChannel channel()返回爲之創建此鍵的通道。  SelectableChannel 是ServerSocketChannel的父類
            ServerSocketChannel ss = (ServerSocketChannel) key.channel();//表示服務器通道
            System.out.println("11111");
            //9.獲取客戶端通道
            SocketChannel sc = ss.accept();
            System.out.println("22222");
            //10.創建緩衝區
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //11.讀取客戶端發送的數據
            int len = sc.read(buffer);
            //12.將buffer轉換爲普通數組
            byte[] arr = buffer.array();
            //13.輸出客戶端發送的數據
            System.out.println(new String(arr,0,len));
        }
        //14.釋放資源
        ssc.close();
    }
}

5.Selector選擇器管理多個通道

客戶端:

package com.itheima.sh.selector_06;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/*
    客戶端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.創建客戶端對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
        //2.創建緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //3.添加數據
        buffer.put("大家好,我是nio,我來了,我難嗎".getBytes());
        //4.切換讀模式
        buffer.flip();
        //5.寫數據
        sc.write(buffer);
        //6.釋放資源
        sc.close();
    }
}

服務器:

package com.itheima.sh.selector_06;

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

/*
    服務器端
    Selector選擇器管理多個通道
 */
public class ServerDemo01 {
    public static void main(String[] args) throws IOException {
        //1.創建服務器對象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(7777));

        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        ssc1.bind(new InetSocketAddress(8888));

        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        ssc2.bind(new InetSocketAddress(9999));

        //2.設置非阻塞
        ssc.configureBlocking(false);
        ssc1.configureBlocking(false);
        ssc2.configureBlocking(false);

        //3.創建選擇器
        Selector selector = Selector.open();

        //4.將通道註冊到選擇器上
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        ssc1.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);

        //加死循環模擬一直運行
        while(true){
            //5.連接客戶端
            System.out.println("11111");
            selector.select();

            //6.獲取所有被連接的服務器對象
            Set<SelectionKey> set1 = selector.selectedKeys();
            //            System.out.println("服務器個數:"+set1.size());
            //            //7.遍歷set集合
            for (SelectionKey key : set1) {
                //8.獲取服務器對象
                ServerSocketChannel ss = (ServerSocketChannel) key.channel();
                //9.取出客戶端
                SocketChannel s = ss.accept();
                System.out.println("s = " + s);
                //10.定義緩衝區
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //11.使用客戶端對象s調用讀取方法讀取客戶端的數據放到buffer中
                int len = s.read(buffer);
                //12.轉換普通數組
                byte[] arr = buffer.array();
                //13.輸出
                System.out.println(new String(arr,0,len));
            }
        }


    }
}

問題圖解:

在這裏插入圖片描述

產生上述異常原因:

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

解決上述問題:

package com.itheima.sh.selector_06;

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

/*
    服務器端
    Selector選擇器管理多個通道
 */
public class ServerDemo01 {
    public static void main(String[] args) throws IOException {
        //1.創建服務器對象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(7777));

        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        ssc1.bind(new InetSocketAddress(8888));

        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        ssc2.bind(new InetSocketAddress(9999));

        //2.設置非阻塞
        ssc.configureBlocking(false);
        ssc1.configureBlocking(false);
        ssc2.configureBlocking(false);

        //3.創建選擇器
        Selector selector = Selector.open();

        //4.將通道註冊到選擇器上
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        ssc1.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);

        //加死循環模擬一直運行
        while (true) {
            //5.連接客戶端
            System.out.println("11111");
            selector.select();

            //6.獲取所有被連接的服務器對象
            Set<SelectionKey> set1 = selector.selectedKeys();
            System.out.println("服務器個數:" + set1.size());
            //            //7.遍歷set集合
            /*
                對於set1集合來說,取出每個服務器對象使用完畢之後需要將其從set集合中刪除
                注意刪除集合中的數據,我們這裏是增強for循環,原理是Iterator迭代器,刪除集合數據不能使用集合中的刪除方法,
                否則會報併發修改異常,使用Iterator迭代器中的刪除方法
             */
            //獲取迭代器對象
            Iterator<SelectionKey> it = set1.iterator();
            while (it.hasNext()) {
                //取出SelectionKey
                SelectionKey key = it.next();
//                for (SelectionKey key : set1) {//8888 9999
                //8.獲取服務器對象
                ServerSocketChannel ss = (ServerSocketChannel) key.channel();
                //9.取出客戶端
                SocketChannel s = ss.accept();
                System.out.println("s = " + s);
                //10.定義緩衝區
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //11.使用客戶端對象s調用讀取方法讀取客戶端的數據放到buffer中
                int len = s.read(buffer);
                //12.轉換普通數組
                byte[] arr = buffer.array();
                //13.輸出
                System.out.println(new String(arr, 0, len));
                //刪除服務器對象 使用迭代器中的刪除方法
                it.remove();
//            }
            }
//
        }


    }
}

小結:每次處理完客戶端之後,都使用迭代器中的刪除方法將對應的服務器對象從set集合中刪除。

4.NIO2-AIO(異步、非阻塞)(理解)

概念介紹

從jdk7開始引入的技術,稱爲NIO2英文全稱:Asynchronous I/O 異步的IO.異步 IO 是基於事件和回調機制實現的,也就是應用操作之後會直接返回,不會堵塞在那裏,當後臺處理完成,操作系統會通知相應的線程進行後續的操作。

1.同步:調用方法要有返回值。

2.異步:調用方法沒有返回值,並且可以支持回調函數。回調函數就是回過頭來在調用的函數,有底層調用的,我們只負責編寫代碼。

3.阻塞:不執行其他操作,一直等待。

4.非阻塞:不一直等待,可以執行其他操作。

上述四個概念舉例:

在這裏插入圖片描述

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

客戶端:

package com.itheima.sh.aio_07;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/*
    客戶端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.創建客戶端對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 12306));
        //2.創建緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //3.添加數據
        buffer.put("大家好,我是Aio,我來了,我難嗎".getBytes());
        //4.切換讀模式
        buffer.flip();
        //5.寫數據
        sc.write(buffer);
        //6.釋放資源
        sc.close();
    }
}

服務端:

package com.itheima.sh.aio_07;

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

/*
    服務器:
    AIO同步(有返回值)寫法
    使用步驟:
    1.創建aio的服務器AsynchronousServerSocketChannel的對象:
        static AsynchronousServerSocketChannel open() 打開異步服務器套接字通道。
        AsynchronousServerSocketChannel bind(SocketAddress local) 綁定端口  SocketAddress屬於抽象類,我們使用子類InetSocketAddress(端口號)
    2.偵聽並獲取客戶端套接字:
        abstract Future<AsynchronousSocketChannel> accept() 接受連接
        說明:
            1)該方法將接收的客戶端存儲到Future<V>接口中,需要使用該接口中的V get() 取出客戶端
            2) AsynchronousSocketChannel表示客戶端套接字
            3)上述accept方法有返回值,屬於同步的
     3.使用客戶端套接字對象調用方法讀取客戶端的數據
            abstract Future<Integer> read(ByteBuffer dst) 從該通道讀取到給定緩衝區的字節序列。
                說明:
                    1)該方法將接收的客戶端的數據存儲到Future<V>接口中,需要使用該接口中的V get() 取出客戶端請求的	數據
                    2)上述read方法有返回值,屬於同步
 */
public class ServerDemo01 {
    public static void main(String[] args) throws Exception {
        //1.創建AIO的服務器對象
        AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
        //2.綁定端口號
        assc.bind(new InetSocketAddress(12306));
        //3.偵聽並獲取客戶端套接字:
        //future中存放的是客戶端套接字AsynchronousSocketChannel對象,我們需要調用get方法獲取
        Future<AsynchronousSocketChannel> f = assc.accept();
        AsynchronousSocketChannel ascoket = f.get();

        //4.使用客戶端套接字對象調用方法讀取客戶端的數據
        //  abstract Future<Integer> read(ByteBuffer dst) 從該通道讀取到給定緩衝區的字節序列。
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        Future<Integer> f2 = ascoket.read(buffer);
        //取出數據
        Integer len = f2.get();//讀取的字節個數
        //5.將字節緩衝區變爲普通數組
        byte[] arr = buffer.array();
        //6.輸出
        System.out.println(new String(arr,0,len));

        ascoket.close();
        assc.close();
    }
}

小結:

1.AsynchronousServerSocketChannel表示AIO中的服務器套接字對象

2.獲取對象:

static AsynchronousServerSocketChannel open() 打開異步服務器套接字通道。
AsynchronousServerSocketChannel bind(SocketAddress local) 綁定端口

3.偵聽並獲取客戶端套接字:

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

4.使用客戶端套接字對象調用方法讀取客戶端的數據

 abstract Future<Integer> read(ByteBuffer dst) 從該通道讀取到給定緩衝區的字節序列。

AIO異步非阻塞連接(理解)

客戶端:

package com.itheima.sh.aio_08;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/*
    客戶端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.創建客戶端對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));

        //6.釋放資源
        sc.close();
    }
}

服務器:

package com.itheima.sh.aio_08;

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.concurrent.Future;

/*
    服務器
    編寫AIO的異步方式:

 */
public class ServerDemo01 {
    public static void main(String[] args) throws Exception {
        //1.創建AIO的服務器對象
        AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
        assc.bind(new InetSocketAddress(9999));

        //2.異步非阻塞接收客戶端
        /*
            abstract <A> void accept​(A attachment, CompletionHandler<AsynchronousSocketChannel> handler)
                        參數:
                            attachment:要附加到I / O操作的對象; 可以是null
                            handler:屬於CompletionHandler接口類型,表示消耗結果的處理程序 ,這裏保存的是客戶端套接字
                                方法:
                                    void completed​(V result, A attachment) 操作完成後調用。  回調函數
                                    void failed​(Throwable exc, A attachment) 當操作失敗時調用。
         */
        System.out.println("111111");
        assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            // 操作完成後調用。  回調函數:回過頭來調用的函數,底層調用的
            /*
                result:表示客戶端
                attachment:就是null
             */
            @Override
            public void completed(AsynchronousSocketChannel result, Object attachment) {
                System.out.println("completed.......");
            }
            // 當操作失敗時調用
            @Override
            public void failed(Throwable exc, Object attachment) {

            }
        });

        System.out.println("222222");
        //爲了能夠讓上述回調函數執行,這裏使用死循環,讓jvm一直運行
        while (true){

        }
    }
}

AIO異步非阻塞連接並讀取客戶端的數據(理解)

客戶端:

package com.itheima.sh.aio_09;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/*
    客戶端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.創建客戶端對象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 12306));
        //2.創建緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //3.添加數據
        buffer.put("大家好,我是Aio,我來了,我難嗎".getBytes());
        //4.切換讀模式
        buffer.flip();
        //5.寫數據
        sc.write(buffer);
        //6.釋放資源
        sc.close();
    }
}

服務器端:

package com.itheima.sh.aio_09;

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.concurrent.Future;

/*
    服務器:
    AIO異步,讀取客戶端的數據
 */
public class ServerDemo01 {
    public static void main(String[] args) throws Exception {
        //1.創建服務器對象
        AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
        assc.bind(new InetSocketAddress(12306));

        //2.異步非阻塞接收客戶端
        System.out.println(1);
        assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            //接收的客戶端
            //socket表示客戶端
            @Override
            public void completed(AsynchronousSocketChannel socket, Object attachment) {
                System.out.println(3);
                //如果想要異步讀取客戶端數據
                /*
                    <A> void read​(ByteBuffer dst, A attachment, CompletionHandler<Integer,? super A> handler)
                                參數:
                                    dst:要傳輸字節的緩衝區 存儲數據的字節緩衝區
                                    attachment:要附加到I / O操作的對象; 可以是null
                                    handler:完成處理程序,屬於CompletionHandler接口類型
                                        抽象方法:
                                            void completed​(V result, A attachment) 操作完成後調用。
                                            void failed​(Throwable exc, A attachment) 當操作失敗時調用。
                 */
                //創建字節數組
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                socket.read(buffer, null, new CompletionHandler<Integer, Object>() {
                    //讀取成功的方法
                    //len表示read讀取的字節個數
                    @Override
                    public void completed(Integer len, Object attachment) {
                        System.out.println(5);
                        //將字節緩衝區變爲普通的字節數組
                        byte[] arr = buffer.array();
                        System.out.println(new String(arr,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) {
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章