NIO的網絡編程

1 Selector選擇器老大

Selector
選擇器,網絡編程使用NIO的大哥!!!
服務器可以執行一個線程,運行Selector程序,進行監聽操作。
新連接, 已經連接, 讀取數據,寫入數據

Selector常用方法:
public static Selector Open();
得到一個選擇器對象
public int select(long timeout);
監聽所有註冊通道,存在IO流操作是,會將對應的信息SelectionKey存入到內部的集
閤中,參數是一個超時時間
public Set selectionKeys();
返回當前Selector內部集合中保存的所有SelectionKey

2 SelectionKey

SelectionKey
表示Selector和網絡通道直接的關係
int OP_ACCEPT; 16 需要連接
int OP_CONNECT; 8 已經連接
int OP_READ; 1 讀取操作
int OP_WRITE; 4 寫入操作
SelectionKey
public abstract Selector selector();
得到與之關聯的 Selector 對象
public abstract SelectableChannel channel();
得到與之關聯的通道
public final Object attachment();
得到與之關聯的共享數據
public abstract SelectionKey interestOps(int ops);
設置或改變監聽事件
public final boolean isAcceptable();
是否可以 accept
public final boolean isReadable();
是否可以讀
public final boolean isWritable();
是否可以寫

1.3 ServerSocketChannel

ServerSocketChannel
服務端Socket程序對應的Channel通道
常用方法:
public static ServerSocketChannel open();
開啓服務器ServerSocketChannel通道,等於開始服務器程序
public final ServerSocketChannel bind(SocketAddress local);
設置服務器端端口號
public final SelectableChannel configureBlocking(boolean block);
設置阻塞或非阻塞模式, 取值 false 表示採用非阻塞模式
public SocketChannel accept();
[非阻塞]
獲取一個客戶端連接,並且得到對應的操作通道
public final SelectionKey register(Selector sel, int ops);
[重點方法]
註冊當前選擇器,並且選擇監聽什麼事件

4 SocketChannel

SocketChannel
客戶端Socket對應的Channel對象

常用方法:
public static SocketChannel open();
打卡一個Socket客戶端Channel對象
public final SelectableChannel configureBlocking(boolean block)
這裏可以設置是阻塞狀態,還是非阻塞狀態
false,表示非阻塞
public boolean connect(SocketAddress remote);
連接服務器
public boolean finishConnect();
如果connect連接失敗,可以通過finishConnect繼續連接
public int write(ByteBuffer buf);
寫入數據到緩衝流中
public int read(ByteBuffer buf); 、
從緩衝流中讀取數據
public final SelectionKey register(Selector sel, int ops, Object attechment);
註冊當前SocketChannel,選擇對應的監聽操作,並且可以帶有Object attachment參數
public final void close();
關閉SocketChannel

5 使用NIO完成一個客戶端和服務器

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 符合TCP協議,非阻塞IO NIO完成對應的客戶端代碼
 *
 * @author Anonymous 2020/3/16 15:10
 */
public class TcpNioClient {
    public static void main(String[] args) throws IOException, InterruptedException {
        // 1. 得到一個網絡通道
        SocketChannel socket = SocketChannel.open();

        // 2. 設置當前NIO採用的方式爲非阻塞方式
        socket.configureBlocking(false);

        // 3. 確定服務器IP地址和對應程序端口號,創建一個InetSocketAddress對象
        InetSocketAddress address = new InetSocketAddress("192.168.31.154", 8848);

        // 4. 連接服務器
        if (!socket.connect(address)) {
            // 如果是false,表示連接失敗,保持申請連接的狀態
            while (!socket.finishConnect()) {
                // 因爲採用NIO非阻塞方式,在獲取等待連接的狀態下,可以去做當前程序的其他操作。
                System.out.println("保持呼叫服務器狀態,但是我還能做點別的事情~~~ 等待2s繼續申請連接~~~");
                Thread.sleep(2000);
            }
        }

        // 5. 準備一個數據存入到緩衝區
        ByteBuffer buffer = ByteBuffer.wrap("你好,服務器,我在等你...".getBytes());

        // 6. 通過SocketChannel 符合TCP協議Socket要求Channel對象發送
        socket.write(buffer);

        new Scanner(System.in).nextLine();
    }
}

5.2 再來完成服務端

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

/**
 * 使用ServerSocketChannel NIO非阻塞方式,完成服務端代碼
 *
 * 1. 開啓服務器
 * 	    ServerScoketChannel
 * 2. 開啓Selector 大哥
 * 	    Selector對象
 * 3. 服務器ServerSocketChannel bind監聽端口號
 * 	    8848端口
 * 4. 設置非阻塞狀態
 * 	    configureBlocking(false)
 * 5. ServerSocketChannel 註冊--> Selector
 * 	    register(selector, OP_ACCEPT);
 *
 * 6. Selector大哥開始忙活
 * 	    6.1 獲取連接,註冊對應的Socket
 * 	    6.2 監聽讀寫事件
 * 		    6.2.1 讀數據。客戶端發送數據到服務器
 * 		    6.2.2 寫數據。發送數據數據給客戶端
 *
 *
 * @author Anonymous 2020/3/16 15:44
 */
public class TcpNioServer {
    public static void main(String[] args) throws IOException {
        // 1. 開啓服務器
        ServerSocketChannel serverSocket = ServerSocketChannel.open();

        // 2. 開啓Selector 大哥
        Selector selector = Selector.open();

        // 3. 服務端代碼綁定端口號
        serverSocket.bind(new InetSocketAddress(8848));

        // 4. 設置非阻塞狀態
        serverSocket.configureBlocking(false);

        // 5. ServerSocketChannel 註冊--> Selector 返回值是一個SelectionKey
        // 並且明確當前Selector監聽SelectionKey.OP_ACCEPT,監聽連接服務器
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);

        // 6. 大哥幹活
        while (true) {
            // 6.1 獲取連接,註冊對應的Socket
            if (0 == selector.select(1000)) {
                // 0 == selector.select(1000) 表示沒有連接到客戶端
                System.out.println("ServerSocket提示,當前沒有客戶端搭理我,我自己默默的畫圈圈~~~");
                continue;
            }

            // 6.2 監聽讀寫事件
            // 得到當前Selector中所有的SelectionKey
            Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
            while (selectionKeys.hasNext()) {

                SelectionKey selectionKey = selectionKeys.next();
                // 6.2.1 判斷客戶端是一個連接請求 OP_ACCEPT
                if (selectionKey.isAcceptable()) {
                    System.out.println("客戶端請求連接!!!");
                    // 獲取對應的Socket,只不過這裏是獲取對應的SocketChannel
                    SocketChannel socket = serverSocket.accept();
                    // 設置當前對應客戶端的SocketChannel對象是一個非阻塞狀態
                    socket.configureBlocking(false);
                    /*
                     註冊當前Socket對象
                     selector 註冊到當前的Selector【核心】
                     SelectionKey.OP_READ 選擇當前Socket監聽的操作內容是OP_READ 從當前Socket中讀取數據
                     ByteBuffer.allocate(1024 * 4) attachment 補充參數,這裏是給予當前Socket對象一個4KB 字節緩衝區對象
                     */
                    socket.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024 * 4));
                }

                // 6.2.2 判斷客戶端目前是可讀狀態,獲取客戶端發送給服務器的數據
                if (selectionKey.isReadable()) {
                    // 從SelectionKey 中獲取對應的SocketChannel對象
                    SocketChannel socket = (SocketChannel) selectionKey.channel();

                    // 因爲使用的是NIO,涉及到Channel和ByteBuffer,數據在緩衝區中
                    ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();

                    // 讀取數據。利用SocketChannel從緩衝中ByteBuffer中讀取數據。
                    socket.read(buffer);

                    System.out.println("客戶端發送數據:" + new String(buffer.array()));
                }

                // 處理完進行一個移除當前SelectionKey操作
                selectionKeys.remove();
            }
        }


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