標籤:
部分內容引用自xpbug的Blog。
說到socket伺服器,第一反應是java.net.Socket這個類。事實上在並發和回應時間要求不高的場合,是可以用java.net.Socket來實現的,比如寫一個區域網路聊天工具、傳送檔案等。但它的缺點也很明顯,需要自行對接受的線程進行維護,管理緩衝區的分配等,我嘗試過用java.net.Socket完成一個瞬時負載在千人左右的伺服器,卻因後期改動和維護異常麻煩而放棄。
Java自1.4以後,加入了新IO特性,這便是本文要介紹的NIO。下面是一段伺服器的範例程式碼:
1 public class EchoServer { 2 public static SelectorLoop connectionBell; 3 public static SelectorLoop readBell; 4 public boolean isReadBellRunning=false; 5 6 public static void main(String[] args) throws IOException { 7 new EchoServer().startServer(); 8 } 9 10 // 啟動伺服器 11 public void startServer() throws IOException { 12 // 準備好一個鬧鐘.當有連結進來的時候響. 13 connectionBell = new SelectorLoop(); 14 15 // 準備好一個鬧裝,當有read事件進來的時候響. 16 readBell = new SelectorLoop(); 17 18 // 開啟一個server channel來監聽 19 ServerSocketChannel ssc = ServerSocketChannel.open(); 20 // 開啟非阻塞模式 21 ssc.configureBlocking(false); 22 23 ServerSocket socket = ssc.socket(); 24 socket.bind(new InetSocketAddress("localhost",7878)); 25 26 // 給鬧鐘規定好要監聽報告的事件,這個鬧鐘只監聽新串連事件. 27 ssc.register(connectionBell.getSelector(), SelectionKey.OP_ACCEPT); 28 new Thread(connectionBell).start(); 29 } 30 31 // Selector輪詢線程類 32 public class SelectorLoop implements Runnable { 33 private Selector selector; 34 private ByteBuffer temp = ByteBuffer.allocate(1024); 35 36 public SelectorLoop() throws IOException { 37 this.selector = Selector.open(); 38 } 39 40 public Selector getSelector() { 41 return this.selector; 42 } 43 44 @Override 45 public void run() { 46 while(true) { 47 try { 48 // 阻塞,只有當至少一個註冊的事件發生的時候才會繼續. 49 this.selector.select(); 50 51 Set<SelectionKey> selectKeys = this.selector.selectedKeys(); 52 Iterator<SelectionKey> it = selectKeys.iterator(); 53 while (it.hasNext()) { 54 SelectionKey key = it.next(); 55 it.remove(); 56 // 處理事件. 可以用多線程來處理. 57 this.dispatch(key); 58 } 59 } catch (IOException e) { 60 e.printStackTrace(); 61 } catch (InterruptedException e) { 62 e.printStackTrace(); 63 } 64 } 65 } 66 67 public void dispatch(SelectionKey key) throws IOException, InterruptedException { 68 if (key.isAcceptable()) { 69 // 這是一個connection accept事件, 並且這個事件是註冊在serversocketchannel上的. 70 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); 71 // 接受一個串連. 72 SocketChannel sc = ssc.accept(); 73 74 // 對新的串連的channel註冊read事件. 使用readBell鬧鐘. 75 sc.configureBlocking(false); 76 sc.register(readBell.getSelector(), SelectionKey.OP_READ); 77 78 // 如果讀取線程還沒有啟動,那就啟動一個讀取線程. 79 synchronized(EchoServer.this) { 80 if (!EchoServer.this.isReadBellRunning) { 81 EchoServer.this.isReadBellRunning = true; 82 new Thread(readBell).start(); 83 } 84 } 85 86 } else if (key.isReadable()) { 87 // 這是一個read事件,並且這個事件是註冊在socketchannel上的. 88 SocketChannel sc = (SocketChannel) key.channel(); 89 // 寫資料到buffer 90 int count = sc.read(temp); 91 if (count < 0) { 92 // 用戶端已經中斷連線. 93 key.cancel(); 94 sc.close(); 95 return; 96 } 97 // 切換buffer到讀狀態,內部指標歸位. 98 temp.flip(); 99 String msg = Charset.forName("UTF-8").decode(temp).toString();100 System.out.println("Server received ["+msg+"] from client address:" + sc.getRemoteAddress());101 102 Thread.sleep(1000);103 // echo back.104 sc.write(ByteBuffer.wrap(msg.getBytes(Charset.forName("UTF-8"))));105 106 // 清空buffer107 temp.clear();108 }109 }111 }112 113 }
OK,原文的注釋已經很詳細了,這裡進一步解析這段代碼。
首先是java.nio.channels.ServerSocketChannel這個類
Java NIO 非阻塞Socket伺服器構建