來源:http://blog.sina.com.cn/s/blog_81c2545a01011afh.html html
在使用傳統的ServerSocket和Socket的時候不少時候程序是會阻塞的 服務器
好比 serversocket.accept() , socket.getInputStream().read() 的時候都會阻塞 accept()方法除非等到客戶端socket的鏈接或者被異常中斷 不然會一直等待下去 socket
read()方法也是如此 除非在輸入流中有了足夠的數據不然該方法也會一直等待下去知道數據的到來.在ServerSocket與Socket的方式中服務器端每每要爲每個客戶端(socket)分配一個線程,而每個線程都有可能處於長時間的阻塞狀態中.而過多的線程也會影響服務器的性能.在JDK1.4引入了非阻塞的通訊方式,這樣使得服務器端只須要一個線程就能處理全部客戶端socket的請求. 性能
下面是幾個須要用到的核心類 spa
ServerSocketChannel: ServerSocket 的替代類, 支持阻塞通訊與非阻塞通訊. 線程
SocketChannel: Socket 的替代類, 支持阻塞通訊與非阻塞通訊. server
Selector: 爲ServerSocketChannel 監控接收客戶端鏈接就緒事件, 爲 SocketChannel 監控鏈接服務器就緒, 讀就緒和寫就緒事件. htm
SelectionKey: 表明 ServerSocketChannel 及 SocketChannel 向 Selector 註冊事件的句柄. 當一個 SelectionKey 對象位於Selector 對象的 selected-keys 集合中時, 就表示與這個 SelectionKey 對象相關的事件發生了.在SelectionKey 類中有幾個靜態常量 對象
SelectionKey.OP_ACCEPT ->客戶端鏈接就緒事件 等於監聽serversocket.accept()返回一個socket blog
SelectionKey.OP_CONNECT ->準備鏈接服務器就緒 跟上面相似,只不過是對於socket的至關於監聽了 socket.connect()
SelectionKey.OP_READ ->讀就緒事件, 表示輸入流中已經有了可讀數據, 能夠執行讀操做了
SelectionKey.OP_WRITE ->寫就緒事件
下面是服務器端:
Selector selector = Selector.open(); //靜態方法 實例化selector
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); //設置爲非阻塞方式,若是爲true 那麼就爲傳統的阻塞方式
serverChannel.socket().bind(new InetSocketAddress(port)); //綁定IP 及 端口
serverChannel.register(selector, SelectionKey.OP_ACCEPT); //註冊 OP_ACCEPT事件
new ServerThread().start(); //開啓一個線程 處理全部請求
ServerThread中的run方法
view plainprint?
public void run()
{
while(true)
{
try
{
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
SocketChannel sc ;
while(iter.hasNext())
{
SelectionKey key = iter.next();
if(key.isAcceptable()); // 新的鏈接
else if(key.isReadable()) ;// 可讀
iter.remove(); //處理完事件的要從keys中刪去
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
其中在 isAcceptable()中經過 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel sc = ssc.accept(); 獲得客戶端的SocketChannel
在isReadable()中SocketChannel sc = (SocketChannel) key.channel(); 獲得SocketChannel .
在SocketChannel 對象中能夠用write() read() 進行讀寫操做 只不過操做的對象再也不是byte[] String之類 而是ByteBuffer
客戶端基本同樣
selector = Selector.open();
channel = SocketChannel.open(new InetSocketAddress(port));
channel.configureBlocking(false);
channel.register(selector,SelectionKey.OP_CONNECT);
new ClientThread().start();
run方法
while (true)
{
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while(iter.hasNext())
{
SelectionKey key = iter.next();
if(key.isConnectable());//鏈接成功&正常
else if(key.isReadable())//可讀
iter.remove();
}
能夠經過key.channel();方法獲得當前的socketchannel對象
總結其實這裏將阻塞變爲非阻塞實際是用一個while死循環來處理的
首先經過seleector.select()從新獲得事件只要有事件不管是什麼 都交給循環體去處理 在循環體中分別進行不一樣的處理
而多個socket經過一個seleector進行贊成管理
while(一直等待, 直到有接收鏈接就緒事件, 讀就緒事件或寫就緒事件發生){ //阻塞
if(有客戶鏈接)
接收客戶的鏈接; //非阻塞
if(某個 Socket 的輸入流中有可讀數據)
從輸入流中讀數據; //非阻塞
if(某個 Socket 的輸出流能夠寫數據)
向輸出流寫數據; //非阻塞
}
相似這樣 以上處理流程採用了輪詢的工做方式, 當某一種操做就緒時, 就執行該操做, 不然就查看是否還有其餘就緒的操做能夠執行. 線程不會由於某一個操做尚未就緒, 就進入阻塞狀態, 一直傻傻地在那裏等待這個操做就緒.