Java NIO學習(三)Selector監聽事件+NIO服務器實例

書接上文

上一篇博客說了一下SelectionKey這個對象的一些常用屬性,爲這一篇文章的NIO服務器實例算是做了一些鋪墊。不過在展示服務端實例代碼之前,還要說幾點細節上的東西。

Selector如何選擇就緒的通道

//這個方法可能會阻塞,直到至少有一個已註冊的事件發生,或者當一個或者更多的事件發生時
selector.select();

NIO明明是非阻塞的IO,爲何會阻塞

這是第一章Java NIO學習(一)NIO相關概念中說的內容,作爲非阻塞的IO操作,NIO爲何會有這麼一個可能會造成阻塞的方法呢,別急,selector對象有很多方法可以解決阻塞的問題!

//阻塞在select()方法上的線程也可以立刻返回,不阻塞
selector.selectNow();

//可以設置超時時間,防止進程阻塞
selector.select(long timeout);

//可以喚醒阻塞狀態下的selector
selector.wakeup();

NIO服務端實例

本代碼實例模擬一個遊戲服務器,監聽玩家上線的事件。

package nio;

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

public class NIOServer {
    private int port;
    //聲明通道
    private ServerSocketChannel server;
    //Selector
    private Selector selector;
    //緩衝區
    private ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);

    public NIOServer(int port){
        this.port = port;
        try{
            server = ServerSocketChannel.open();
            //非阻塞
            server.configureBlocking(false);
            //服務端通道綁定地址
            server.bind(new InetSocketAddress("127.0.0.1", this.port));
            selector = Selector.open();
            //給服務器通道註冊接收操作,可以在這個點接收客戶端連接
            server.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("[系統消息提示]NIO服務器初始化完畢,監聽端口" + this.port);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //監聽客戶端連接
    public void Listener(){
        while(true){
            try {
                //這個方法可能會阻塞,直到至少有一個已註冊的事件發生,或者當一個或者更多的事件發生時
                //selector.select(long timeout);可以設置超時時間,防止進程阻塞
                //selector.wakeup();可以喚醒阻塞狀態下的selector
                //selector.selectNow();也可以立刻返回,不阻塞
                selector.select();
                //輪詢所有選擇器接收到的操作
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> selectionKeyIte = selectionKeys.iterator();
                while(selectionKeyIte.hasNext()){
                    SelectionKey selectionKey = selectionKeyIte.next();
                    selectionKeyIte.remove();
                    //業務處理
                    handleKey(selectionKey);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //業務處理
    public void handleKey(SelectionKey selectionKey) throws IOException{
        //服務端的channel
        ServerSocketChannel server = null;
        //獲得和客戶端連接的通道
        SocketChannel channel = null;
        //接收到的信息
        String receiveText;
        //數據的標記位,判斷客戶端是否關閉
        int count;
        //客戶端請求連接事件
        if(selectionKey.isAcceptable()){
            try {
                server = (ServerSocketChannel) selectionKey.channel();
                //獲得服務端和客戶端連接的通道
                channel = server.accept();
                //將服務端和客戶端連接的設置非阻塞
                channel.configureBlocking(false);
                //註冊服務端和客戶端通道的讀事件
                channel.register(selector, SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if(selectionKey.isReadable()){
            //獲取通道對象,方便後面將通道內的數據讀入緩衝區
            channel = (SocketChannel) selectionKey.channel();
            receiveBuffer.clear();
            count = channel.read(receiveBuffer);
            //如果讀出來的客戶端數據不爲空
            if(count>0){
                receiveText = new String(receiveBuffer.array(),0,count);
                System.out.println("[系統消息提示]服務器發現["+receiveText+"]玩家上線");
            }else{
                System.out.println("[系統消息提示]玩家下線");
                //檢測到客戶端關閉(玩家下線),刪除該selectionKey監聽事件,否則會一直收到這個selectionKey的動作
                selectionKey.cancel();
            }
        } 
    }

    public static void main(String[] args) {
        int port = 7080;
        NIOServer server = new NIOServer(port);
        server.Listener();
    }
}

運行效果

啓動服務器
這裏寫圖片描述

玩家上線
打開windows的cmd,用telnet命令模擬客戶端玩家上線訪問服務器。

telnet 127.0.0.1 7080

這裏寫圖片描述

這裏稍微提一句,博主用的是win7系統,win7的telnet命令有點問題,輸入完telnet 127.0.0.1 7080命令後,正常輸入的話,服務端只能接受輸入的第一個字符,所以要通過“Ctrl+}”(回車鍵左邊第一個按鍵)來進入telnet的命令行界面,通過“send”命令去輸入玩家姓名傳給服務器。

玩家下線
關閉一個telnet窗口
這裏寫圖片描述

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