java NIO實例學習

ServerSocketChannel和SocketChannel

   在使用傳統的ServerSocket和Socket的時候 很多時候程序是會阻塞的,比serverSocket.accept(),socket.getInputStream.read()

會阻塞accept(),除非等到客戶端socket的連接或者異常中斷,否則會一直等待下去。read()方法也是如此,除非在輸入流中有了足夠數據,否則該方法也會一直等待下去直到數據的到來。在ServerSocket和Socket方法中,服務器往往要爲客戶端分配一個線程,而每一個線程都有可能處於長時間的阻塞狀態中,而過多的線程也會影響服務器的性能。因此在JDK1.4中引入了非阻塞的通信方式,這樣使得服務器端只需要一個線程就能處理所有客戶端Socket的請求。


通信實例

   上一篇的最後講了在實例中需要用到的核心類。在這裏就不再敘述了。

下面是一個該通信模式下的實例。首先建一個服務器端:


 public class NIOServer2{
    /*標識數字*/
    private  int flag = 0;
    /*緩衝區大小*/
    private  int BLOCK = 4096;
    /*接受數據緩衝區*/
    private  ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
    /*發送數據緩衝區*/
    private  ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
    private  Selector selector;

    public NIOServer2(int port) throws IOException {
        // 打開服務器套接字通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 服務器配置爲非阻塞
        serverSocketChannel.configureBlocking(false);
        // 檢索與此通道關聯的服務器套接字
        ServerSocket serverSocket = serverSocketChannel.socket();
        // 進行服務的綁定
        serverSocket.bind(new InetSocketAddress(port));
        // 通過open()方法找到Selector
        selector = Selector.open();
        // 註冊到selector,等待連接
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server Start----8888:");
    }


    // 監聽
    private void listen() throws IOException {
        while (true) {
            // 選擇一組鍵,並且相應的通道已經打開
            selector.select();
            // 返回此選擇器的已選擇鍵集。
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                handleKey(selectionKey);
            }
       
        }
    }

    // 處理請求
    private void handleKey(SelectionKey selectionKey) throws IOException {
        // 接受請求
        ServerSocketChannel server = null;
        SocketChannel client = null;
        String receiveText;
        String sendText;
        int count=0;
        // 測試此鍵的通道是否已準備好接受新的套接字連接。
        if (selectionKey.isAcceptable()) {
             System.out.println("服務器端的通道處於可連接狀態。。。");
            // 返回爲之創建此鍵的通道。
            server = (ServerSocketChannel) selectionKey.channel();
            // 接受到此通道套接字的連接。
            // 此方法返回的套接字通道(如果有)將處於阻塞模式。
            client = server.accept();
            // 配置爲非阻塞
            client.configureBlocking(false);
            // 註冊到selector,等待連接
            client.register(selector, SelectionKey.OP_READ);
        } else if (selectionKey.isReadable()) {
            System.out.println("服務器端可讀。。。");
            // 返回爲之創建此鍵的通道。
            client = (SocketChannel) selectionKey.channel();
            //將緩衝區清空以備下次讀取
            receivebuffer.clear();
            //讀取服務器發送來的數據到緩衝區中
            count = client.read(receivebuffer);
            if (count > 0) {
                receiveText = new String( receivebuffer.array(),0,count);
                System.out.println("服務器端接受客戶端數據--:"+receiveText);
                client.register(selector, SelectionKey.OP_WRITE);
            }
        } else if (selectionKey.isWritable()) {
            System.out.println("服務器端可寫。。。。");
            //將緩衝區清空以備下次寫入
            sendbuffer.clear();
            // 返回爲之創建此鍵的通道。
            client = (SocketChannel) selectionKey.channel();
            sendText="message from server--" + flag++;
            //向緩衝區中輸入數據
            sendbuffer.put(sendText.getBytes());
            //將緩衝區各標誌復位,因爲向裏面put了數據標誌被改變要想從中讀取數據發向服務器,就要復位
            sendbuffer.flip();
            //輸出到通道
            client.write(sendbuffer);
            System.out.println("服務器端向客戶端發送數據--:"+sendText);
            client.register(selector, SelectionKey.OP_READ);
        }


    }

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        int port = 1234;
        NIOServer2 server = new NIOServer2(port);
        server.listen();
    }
}
在該類中可以看到,建立服務器的方法是:首先打開服務器套接字通道,並進行服務綁定,然後註冊到selector,等待客戶端的連接。然後就是監聽,實時監聽通道的狀態。監聽的方法是   選擇註冊的一組鍵,該組鍵是一個集合,從中選出已選擇的鍵集,然後通過迭代對每一個鍵進行處理。 

對已選擇鍵進行處理:首先判斷其正處的狀態:連接就緒、讀就緒、寫就緒。對不同的狀態作相應的處理


客戶端:

public class NIOClient {

    /*標識數字*/
    private static int flag = 0;
    /*緩衝區大小*/
    private static int BLOCK = 4096;
    /*接受數據緩衝區*/
    private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
    /*發送數據緩衝區*/
    private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
    /*服務器端地址*/
    private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(
            "localhost", 1234);

    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        // 打開socket通道
        SocketChannel socketChannel = SocketChannel.open();
        // 設置爲非阻塞方式
        socketChannel.configureBlocking(false);
        // 打開選擇器
        Selector selector = Selector.open();
        // 註冊連接服務端socket動作
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        // 連接
        socketChannel.connect(SERVER_ADDRESS);
        // 分配緩衝區大小內存

        Set<SelectionKey> selectionKeys;
        Iterator<SelectionKey> iterator;
        SelectionKey selectionKey;
        SocketChannel client;
        String receiveText;
        String sendText;
        int count=0;

        while (true) {
            //選擇一組鍵,其相應的通道已爲 I/O 操作準備就緒。
            //此方法執行處於阻塞模式的選擇操作。
            selector.select();
            //返回此選擇器的已選擇鍵集。
            selectionKeys = selector.selectedKeys();
            //System.out.println(selectionKeys.size());
            iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                selectionKey = iterator.next();
                if (selectionKey.isConnectable()) {
                    System.out.println("client connect");
                    client = (SocketChannel) selectionKey.channel();
                    // 判斷此通道上是否正在進行連接操作。
                    // 完成套接字通道的連接過程。
                    if (client.isConnectionPending()) {
                        client.finishConnect();
                        System.out.println("完成連接!");
                        sendbuffer.clear();
                        sendbuffer.put("Hello,Server".getBytes());
                        sendbuffer.flip();
                        client.write(sendbuffer);
                    }
                    client.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    System.out.println("客戶端可讀。。。。");
                    client = (SocketChannel) selectionKey.channel();
                    //將緩衝區清空以備下次讀取
                    receivebuffer.clear();
                    //讀取服務器發送來的數據到緩衝區中
                    count=client.read(receivebuffer);
                    if(count>0){
                        receiveText = new String( receivebuffer.array(),0,count);
                        System.out.println("客戶端接受服務器端數據--:"+receiveText);
                        client.register(selector, SelectionKey.OP_WRITE);
                    }

                } else if (selectionKey.isWritable()) {
                    System.out.println("客戶端可寫。。。");
                    sendbuffer.clear();
                    client = (SocketChannel) selectionKey.channel();
                    sendText = "message from client--" + (flag++);
                    sendbuffer.put(sendText.getBytes());
                    //將緩衝區各標誌復位,因爲向裏面put了數據標誌被改變要想從中讀取數據發向服務器,就要復位
                    sendbuffer.flip();
                    client.write(sendbuffer);
                    System.out.println("客戶端向服務器端發送數據--:"+sendText);
                    client.register(selector, SelectionKey.OP_READ);
                }
            }
            selectionKeys.clear();
        }
    }
}

這是一個不斷循環的通信實例。在具體的項目中根據自己的需求可進行相應的配置。

    

     

     

  


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