Thinking in Java--使用NIO實現非阻塞Socket通訊
Java1.4提供了一種新的IO讀取方式,稱為NIO。NIO中使用了通道和緩衝器的概念,並且以塊的形式運算元據,這樣更接近作業系統IO操作的形式,提高了JavaIO的效率。NIO的核心類有兩個Channel和Buffer。但是其實除了提升了基本IO操作的效能外,NIO還提供了非阻塞IO的功能。這裡先介紹下阻塞IO和非阻塞IO的概念。考慮到應用程式傳送出IO請求,如果這個IO請求會阻塞執行緒(就是執行緒停在這裡直到讀取到了資料再繼續執行下去),那麼就是阻塞IO;如果這個IO請求沒有阻塞執行緒(執行緒發出了IO請求,但是並停在這裡等資料的到來而是先去做別的事情)就稱為非阻塞IO。可以很顯然的看到,非阻塞的IO可以提高程式的效能。這篇部落格下面會先介紹用於Socket通訊的非阻塞IO的具體類,然後再利用這些類實現一個非阻塞的Socket通訊伺服器。
一.用於非阻塞Socket通訊的幾個類
(1).Selector類
它是SelectableChannel物件的多路複用器,所有希望採用非阻塞方式進行通訊的Channel都應該註冊到Selector物件。但是這個類的物件是不能通過呼叫構造器得到的,而是通過這個類靜態的open()方法得到,該方法將使用系統預設的Selector來返回新的Selector。
Selector物件可以同時監聽多個SelectableChannel的IO狀況,是非阻塞IO的核心。一個Selector例項有3個SelectionKey集合。
1)所有的SelectionKey集合:代表了註冊在該Selector上的Channel,這個集合可以通過keys()方法返回
2)被選擇的SelectionKey集合:代表了所有可以通過select()方法獲取的,需要進行IO處理的Channel,這個集合可以通過selectedKeys()返回。
3)被取消的SelectionKey集合:代表了所有被取消註冊關係的Channel,下一次執行select()方法時,這些Channel對應的SelectionKey就會被徹底刪除,程式通常無須直接訪問這個集合。
Selector類還提供了一系列和select()相關的方法,這些方法比較重要,需要了解一下:
int select():監控所有註冊的Channel,當他們中有需要處理的IO操作時,該方法返回,並將對應的SelectionKey加入到被選擇的SelectionKey集合中,並返回這些Channel的數量。
int select(long timeout):可以設定超時時長的select()操作
int selectNow():執行一個立即返回的select()操作,相對與無引數的select()方法而言,該方法不會阻塞執行緒
Selector wakeup():使一個還未返回的select()方法立刻返回。
(2)SelectableChannel類
Selectabel類是一種支援阻塞I/O和非阻塞I/O的通道。應用程式可以呼叫SelectabelChanel的register()方法將其註冊到指定的Selector上。SelectableChannel物件支援阻塞和非阻塞兩種模式,但是預設情況下是阻塞的(所有的Channel預設都是阻塞模式),必須使用非阻塞模式才能支援非阻塞IO。但是不同的SelectableChannel支援的操作是不一樣的,向ServerSocketChannel代表一個ServerSocket,它只支援OP_ACCEPT操作。而SocketChannle代表一個socket,支援OP_READ操作。下面是幾個SelectableChannel常用的方法:
boolean isBlocking():返回該Channel是否為阻塞模式
SelectabelChannel configureBlocking(boolean block):設定是否採用阻塞模式。
int valiOps():返回一個整數值,表示這個Channel所支援的操作。
boolean isRegistered():返回該Channel是否已經註冊在一個或多個Selector上。
(3)SelectionKey類
該類物件代表SelectableChannel和Selector之間的註冊關係。
(4)ServerSocketChannel類
支援非阻塞操作,對應與ServerSocket這個類,支援OP_ACCEPT操作;該類也提供了accept()方法,功能相當於ServerSocket提供的accept()方法。
(5).SocketChannel類
支援非阻塞操作,對應Socket這個類,支援OP_CONNECT,OP_READ和OP_WRITE操作。這個類還實現了ByteChannel,可以通過SocketChannel來讀寫ByteBuffer物件。
二.利用非阻塞IO實現一個聊天室的伺服器
前面我自己寫了一個仿QQ的C/S區域網聊天工具,在這個工具中,伺服器使用SeverSocket進行監聽,每新加入一個人就新建一個socekt與其通訊並且還要單獨為其開啟一個服務執行緒。這樣如果加入的使用者比較多,那麼就要開啟很多的服務執行緒了,伺服器的壓力就會比較大。現在我們用非阻塞IO,伺服器只需要一個執行緒就可以同時與多個客戶端進行通訊。
具體的思路是:原先伺服器中使用ServerSocket進行監聽,現在改用ServerSocketChannel物件進行監聽。原先每接入一個客戶端,就新建一個Socket進行通訊,現在新建一個SocketChannel進行通訊。最重要的是這些SelectableChannel物件,都必須註冊到一個Selector物件上;然後我們只需要檢測這個Selector物件就行了,我們可以呼叫這個Selector物件的select()方法監聽,這樣就可以實時監聽所有客戶端的行為,並可以通過selectedKeys()方法返回需要處理的SelectionKey物件,SelectionKey物件可以判定返回訊息的內容(是連線請求還是具體的訊息),並且這個物件的Channel方法可以返回被選中的客戶端的Channel。更具體的思路見下面的程式碼及註釋:
package IO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.*;
public class NServer {
//用於檢測所有Channel狀態的Selector
private Selector selector = null;
///預設埠
static final int PORT = 30000;
//定義實現編碼和解碼的字符集物件
private Charset charset = Charset.forName("UTF-8");
public void init() throws IOException
{
//Selector物件是不能通過構造器得到的,必須通過靜態的open()方法得到
selector=Selector.open();
//SeverSocktChannel物件也不能通過構造器得到,所以需要通過open()方法開啟
ServerSocketChannel server = ServerSocketChannel.open();
InetSocketAddress isa = new InetSocketAddress("127.0.0.1",PORT);
//將ServerSocketChannel繫結到指定的IP地址
server.bind(isa);
//設定ServerSocketChannel以非阻塞方式工作(預設是阻塞的)
server.configureBlocking(false);
//將server註冊到指定的Selector物件
server.register(selector, SelectionKey.OP_ACCEPT);
//返回值大於0,表示有Channel中含有需要處理的資料
while(selector.select()>0){
//依次處理selector上的每個已選擇的SelectionKey
for(SelectionKey sk : selector.selectedKeys()){
//從selector已選擇的Key集中刪除正在處理的SelectionKey
selector.selectedKeys().remove(sk);
//如果sk對應的Channel包含客戶端的連線請求
if(sk.isAcceptable()){
//呼叫accept方法接受連線,產生服務端的SocketChannel
SocketChannel sc = server.accept();
//設定採用非阻塞模式
sc.configureBlocking(false);
//將該SocketChannel也註冊到selector上去
sc.register(selector, SelectionKey.OP_READ);
//將sk的Channel設定成準備接收其它的請求
sk.interestOps(SelectionKey.OP_ACCEPT);
}
//如果sk對應的Channel有資料需要讀取
if(sk.isReadable()){
//獲取SelectionKey對應的Channel,該Channel中有需要讀取的資料
SocketChannel sc =(SocketChannel)sk.channel();
//定義準備執行讀取資料的ByteBufferer
ByteBuffer buff = ByteBuffer.allocate(1024);
String content="";
try{
while(sc.read(buff)>0){
buff.flip();
content+=charset.decode(buff);
}
//將sk對應的Channel設定成準備洗一次讀取
sk.interestOps(SelectionKey.OP_READ);
}
//如果捕獲到該sk對應的Channel出現了異常,即表明該Channel
//對應的Client出現了異常,所以從Selector中取消掉sk的註冊
catch(IOException e){
//從Selector中刪除掉指定的SelectionKey
sk.cancel();
if(sk.channel()!=null){
sk.channel().close();
}
}
//如果content的長度不為空,即該聊天資訊不為空
if(content.length()>0){
//遍歷該selector中註冊的所有SelectionKey
for(SelectionKey key: selector.keys()){
//獲取key對應的Chanel
Channel targetChannel = key.channel();
//如果該Channel是SocketChannel物件
if(targetChannel instanceof SocketChannel){
//將讀到的內容寫入到Channel中
SocketChannel dest =(SocketChannel)targetChannel;
dest.write(charset.encode(content));
}
}
}
}
}
}
}
}
相關文章
- Java入門系列-25-NIO(實現非阻塞網路通訊)Java
- IO通訊模型(二)同步非阻塞模式NIO(NonBlocking IO)模型模式BloC
- Java基礎(Socket通訊和NIO)Java
- NIO非阻塞程式設計小案例程式設計
- 從linux原始碼看socket的阻塞和非阻塞Linux原始碼
- 從 Linux 原始碼看 socket 的阻塞和非阻塞Linux原始碼
- socket阻塞與非阻塞,同步與非同步、I/O模型非同步模型
- 使用Task實現非阻塞式的I/O操作
- Java非阻塞I/O模型之NIO說明Java模型
- 如何解讀 Java IO、NIO 中的同步阻塞與同步非阻塞?Java
- 【死磕NIO】— 阻塞、非阻塞、同步、非同步,傻傻分不清楚非同步
- c#實現最簡單的socket通訊C#
- linux非阻塞式socket程式設計之select()用法Linux程式設計
- socket通訊
- Android Socket連線,使用Socket進行通訊(Android)Android
- python中非同步非阻塞如何實現Python非同步
- C# 實現socket通訊程式(伺服器端)C#伺服器
- Android Socket 通訊Android
- Java Socket 之 NIOJava
- Linux 阻塞和非阻塞 IO 實驗學習Linux
- 【死磕NIO】— 阻塞IO,非阻塞IO,IO複用,訊號驅動IO,非同步IO,這你真的分的清楚嗎?非同步
- 使用Java實現WebSocket通訊JavaWeb
- 基於 socket.io 快速實現一個實時通訊應用
- socket通訊的建立
- socket.IO通訊
- 程式間通訊(Socket)
- Java多執行緒技術:實現多使用者服務端Socket通訊Java執行緒服務端
- 在 Laravel 中使用 Workerman 進行 socket 通訊Laravel
- tornado原理介紹及非同步非阻塞實現方式非同步
- Zsh 開發指南(第十七篇 使用 socket 檔案和 TCP 實現程式間通訊)TCP
- 阻塞IO與非阻塞IO
- 同步非同步,阻塞非阻塞非同步
- 非同步、同步、阻塞、非阻塞非同步
- socket.io通訊原理
- 簡單的Socket通訊
- Thinking in Java---執行緒通訊+三種方式實現生產者消費者問題ThinkingJava執行緒
- Uniapp 使用 GoEasy 實現 websocket 實時通訊APPGoWeb
- Java通過SSLEngine與NIO實現HTTPS訪問JavaHTTP