NIO入門案例客戶端的代碼具體實現以及詳細註釋

package com.nio.real;

import java.io.IOException;

public class TimeClient {
    public static void main(String[] args)throws IOException {
        // 設置要監聽的端口
        int port = 8785;
        if (args != null && args.length > 0){
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
        }
        // 創建TimeClientHandler線程來處理異步連接和讀寫操作
        new Thread(new TimeClientHandler("localhost", port), "NIO-MultiplexerTimeClient-001").start();

    }
}
package com.nio.real;

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.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class TimeClientHandler implements Runnable{
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop;

    public TimeClientHandler(String host, int port) {
        this.port = port;
        this.host = host == null ? "localhost" : host;
        try {
            // 初始化多路複用器和SocketChannel
            selector = Selector.open();
            socketChannel = SocketChannel.open();
            // 設置爲異步非阻塞模式
            socketChannel.configureBlocking(false);

        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    @Override
    public void run() {
        try {
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        while (!stop){
            try {
                // 設置休眠時間爲1s,無論是否發生讀寫等事件,selector都會每隔1s倍喚醒一次
                selector.select(1000);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();
                SelectionKey key = null;
                // 多路複用器在線程run方法的無限循環體內輪詢準備就緒的key
                while (it.hasNext()){
                     key = it.next();
                     it.remove();
                    try {
                        // 當有就緒的channel時,執行handleInput方法
                        handleInput(key);
                    } catch (Exception e) {
                        if (key != null){
                            key.cancel();
                            if (key.channel() != null){
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }
        }

        // 多路複用關閉後所有註冊在上面的channel和pipe等資源都會被自動去註冊並關閉,所以不需要重複釋放資源
        if (selector != null){
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    private void handleInput(SelectionKey key)throws IOException {
        // 這個key是不是有效的
        if (key.isValid()){
            // 判斷是否連接成功
            SocketChannel sc = (SocketChannel) key.channel();
            // 判斷連接狀態,處於連接狀態說明服務端已經返回ack應答消息
            if (key.isConnectable()){
                // 對連接結果判斷,返回TRUE說明客戶端連接成功
                if (sc.finishConnect()){
                    // 將sc註冊到多路複用器上,註冊SelectionKey.OP_READ,監聽網絡ducaoz,然後將消息發送給服務端
                    sc.register(selector,SelectionKey.OP_READ);
                    doWrite(sc);
                }else {
                    // 連接失敗,進程退出
                    System.exit(1);
                }
            }
            if (key.isReadable()){
                // 預分配1MB接收緩衝區的應答消息
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                // 調用read()方法進行異步讀取操作,將客戶端請求消息讀取到緩衝區
                int readBytes = sc.read(readBuffer);
                // readBytes大於0, 讀到了字節,對字節進行編碼處理
                if (readBytes > 0){
                    // 將緩衝區當前的limit設置爲position,position = 0;用於後續對緩衝區的讀取操作
                    readBuffer.flip();
                    // 根據緩衝區可讀的字節個數創建字節數組
                    // readBuffer.remaining()返回得是新數組的大小,源碼:limit - position
                    byte[] bytes = new byte[readBuffer.remaining()];
                    // 將緩衝區的可讀的字節數組複製到新創建的字節數組中
                    readBuffer.get(bytes);
                    // 對讀取到的消息進行解碼並且打印
                    String body = new String(bytes, "utf-8");
                    System.out.println("現在是:" + body);
                    // 將stop設置爲TRUE,線程退出循環
                    this.stop = true;
                }else if (readBytes < 0){
                    // 對端鏈路關閉
                    key.cancel();
                    sc.close();
                }else
                    ; // 讀到0字節,忽略
            }
        }

    }

    private void doConnect() throws IOException{
        // 如果直接連接成功,則註冊到多路複用器上,發送請求消息,讀應答
        if (socketChannel.connect(new InetSocketAddress(host, port))){
            socketChannel.register(selector, SelectionKey.OP_READ);
            doWrite(socketChannel);
        }else {
            // 沒有直接連接成功,說明服務端沒有返回TCP握手應答消息,但是不代表連接失敗
            // 註冊到多路複用器上,註冊electionKey.OP_CONNECT,當服務端返回TCPsyn-ack消息後,
            // selector就能夠輪訓到這個socketChannel處於連接就緒狀態
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
        }
    }

    private void doWrite(SocketChannel sc)throws IOException {
        // 構造請求消息體
        String str = "QUERY TIME ORDER";
        // 將字符串編碼成字節數組
        byte[] req = str.getBytes();
        // 根據字節數組的容量創建ByteBuffer
        ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
        // 將字節數組複製到緩衝區中
        writeBuffer.put(req);
        // 將緩衝區當前的limit設置爲position,position = 0;用於後續對緩衝區的讀取操作
        writeBuffer.flip();
        // 發送
        sc.write(writeBuffer);
        // 對發送結果進行判斷,如果緩衝區中的消息全部發送完畢,將輸出打印結果
        if (!writeBuffer.hasRemaining()){
            System.out.println("客戶端成功發送: " + str);
        }
    }
}


【1】NIO入門案例使用netty最新版本框架代碼實現及詳細註釋
https://blog.csdn.net/wildwolf_001/article/details/81132896


【2】NIO入門案例服務端的代碼具體實現以及詳細註釋
https://blog.csdn.net/wildwolf_001/article/details/81085938

【3】NIO類庫的簡介
https://blog.csdn.net/wildwolf_001/article/details/81069324
【4】NIO入門案例之分析NIO服務端序列圖
https://blog.csdn.net/wildwolf_001/article/details/81069180

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