Java網路程式設計——使用NIO實現非阻塞Socket通訊
阿新 • • 發佈:2019-01-02
除了普通的Socket與ServerSocket實現的阻塞式通訊外,java提供了非阻塞式通訊的NIO API。先看一下NIO的實現原理。
從圖中可以看出,伺服器上所有Channel(包括ServerSocketChannel和SocketChannel)都需要向Selector註冊,而該Selector則負責監視這些Socket的IO狀態,當其中任意一個或者多個Channel具有可用的IO操作時,該Selector的select()方法將會返回大於0的整數,該整數值就表示該Selector上有多少個Channel具有可用的IO操作,並提供了selectedKeys()方法來返回這些Channel對應的SelectionKey集合。正是通過Selector,使得伺服器端只需要不斷地呼叫Selector例項的select()方法即可知道當前所有Channel是否有需要處理的IO操作。
看個demo
NClient.java
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.nio.charset.Charset; import java.util.Scanner; public class NClient { //定義檢測SocketChannel的Selector物件 private Selector selector=null; //定義處理編碼和解碼的字符集 private Charset charset=Charset.forName("UTF-8"); //客戶端SocketChannel private SocketChannel sc=null; public void init() throws IOException{ selector=Selector.open(); InetSocketAddress isa=new InetSocketAddress("127.0.0.1",30000); //呼叫open靜態方法建立連線到指定主機的SocketChannel sc=SocketChannel.open(isa); //設定該sc以非阻塞方式工作 sc.configureBlocking(false); //將Socketchannel物件註冊到指定Selector sc.register(selector, SelectionKey.OP_READ); //啟動讀取伺服器端資料的執行緒 new ClientThread().start(); //建立鍵盤輸入流 Scanner scan=new Scanner(System.in); while(scan.hasNextLine()){ //讀取鍵盤輸入 String line=scan.nextLine(); //將鍵盤輸入的內容輸出到SocketChannel中 sc.write(charset.encode(line)); } } //定義讀取伺服器資料的執行緒 private class ClientThread extends Thread{ public void run(){ try{ while(selector.select()>0){ //遍歷每個有可用IO操作Channel對應的SelectionKey for(SelectionKey sk:selector.selectedKeys()){ //刪除正在處理的SelectionKey selector.selectedKeys().remove(sk); //如果該SelectionKey對應的Channel中有可讀的資料 if(sk.isReadable()){ //使用NIO讀取channel中的資料 SocketChannel sc=(SocketChannel) sk.channel(); ByteBuffer buff=ByteBuffer.allocate(1024); String content=""; while(sc.read(buff)>0){ //sc.read(buff); buff.flip(); content+=charset.decode(buff); } //列印輸出讀取的內容 System.out.println("聊天資訊"+content); //為下一次讀取做準備 sk.interestOps(SelectionKey.OP_READ); } } } }catch(IOException ex){ ex.printStackTrace(); } } } public static void main(String[]args) throws IOException{ new NClient().init(); } }
NServer.java
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; 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; public class NServer { //用於檢測所有Channel狀態的Selector private Selector selector=null; //定義實現編碼、解碼的字符集物件 private Charset charset=Charset.forName("UTF-8"); public void init() throws IOException{ selector=Selector.open(); //通過open方法來開啟一個未繫結的ServerSocketChannel例項 ServerSocketChannel server=ServerSocketChannel.open(); InetSocketAddress isa=new InetSocketAddress("127.0.0.1",30000); //將該ServerSocketChannel繫結到指定ip地址 server.socket().bind(isa); //設定ServerSocket以非阻塞方式工作 server.configureBlocking(false); //將server註冊到指定Selector物件 server.register(selector, SelectionKey.OP_ACCEPT); while(selector.select()>0){ //依次處理selector上的每個已選擇的SelectionKey for(SelectionKey sk:selector.selectedKeys()){ //從selector上的已選擇Key集中刪除正在處理的SelectionKey selector.selectedKeys().remove(sk); //如果sk對應的通訊包含客戶端的連線請求 if(sk.isAcceptable()){ //呼叫accept方法接受連線,產生伺服器端對應的SocketChannel SocketChannel sc=server.accept(); //設定採用非阻塞模式 sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); //將sk對應的Channel設定成準備接受其他請求 sk.interestOps(SelectionKey.OP_ACCEPT); } //如果sk對應的通道有資料需要讀取 if(sk.isReadable()){ //獲取該SelectionKey對應的Channel,該Channel中有可讀的資料 SocketChannel sc=(SocketChannel) sk.channel(); //定義準備之星讀取資料的ByteBuffer ByteBuffer buff=ByteBuffer.allocate(1024); String content=""; //開始讀取資料 try{ while(sc.read(buff)>0){ buff.flip(); content+=charset.decode(buff); } //列印從該sk對應的Channel裡讀到的資料 System.out.println("=========="+content); //將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的長度大於0,即聊天資訊不為空 if(content.length()>0){ //遍歷該selector裡註冊的所有SelectKey for(SelectionKey key:selector.keys()){ //選取該key對應的Channel Channel targetChannel=key.channel(); //如果該channel是SocketChannel物件 if(targetChannel instanceof SocketChannel){ //將獨到的內容寫入該Channel中 SocketChannel dest=(SocketChannel) targetChannel; dest.write(charset.encode(content)); } } } } } } } public static void main(String[]args) throws IOException{ new NServer().init(); } }
通過java提供的NIO實現非阻塞Socket通訊,大大提高了網路伺服器的效能。