Java NIO增加了新的SocketChannel、ServerSocketChannel等類來提供對構建高效能的服務端程式的支援。 SocketChannel、ServerSocketChannel能夠在非阻塞的模式下工作,它們都是selectable的類。在構建伺服器或者中介軟體時,推薦使用Java NIO。
在傳統的網路編程中,我們通常使用一個專用線程(Thread)來處理一個Socket串連,通過使用NIO,一個或者很少幾個Socket線程就可以處理成千上萬個活動的Socket串連。
通常情況下,通過ServerSocketChannel.open()獲得一個ServerSocketChannel的執行個體,通過SocketChannel.open或者serverSocketChannel.accept()獲得一個SocketChannel執行個體。要使ServerSocketChannel或者SocketChannel在非阻塞的模式下操作,可以調用
serverSocketChannel.configureBlocking (false);
或者
socketChannel.configureBlocking (false);
語句來達到目的。通常情況下,服務端可以使用非阻塞的ServerSocketChannel,這樣,服務端的程式就可以更容易地同時處理多個socket線程。
下面我們來看一個綜合例子,這個例子使用了ServerSocketChannel、SocketChannel開發了一個非阻塞的、能處理多線程的Echo服務端程式,見樣本12-14。
【程式原始碼】
1 // ==================== Program Discription =====================2 // 程式名稱:樣本12-14 : SocketChannelDemo.java3 // 程式目的:學習Java NIO#SocketChannel4 // ==============================================================5 6 7 import java.nio.ByteBuffer;8 import java.nio.channels.ServerSocketChannel;9 import java.nio.channels.SocketChannel;10 import java.nio.channels.Selector;11 import java.nio.channels.SelectionKey;12 import java.nio.channels.SelectableChannel;13 14 import java.net.Socket;15 import java.net.ServerSocket;16 import java.net.InetSocketAddress;17 import java.util.Iterator;1819 public class SocketChannelDemo 20 21 {22 public static int PORT_NUMBER = 23;//監聽連接埠23 ServerSocketChannel serverChannel;24 ServerSocket serverSocket ;25 Selector selector ;26 private ByteBuffer buffer = ByteBuffer.allocateDirect (1024);27 28 public static void main (String [] args)29 throws Exception30 {31 SocketChannelDemo server=new SocketChannelDemo();32 server.init(args);33 server.startWork();34 }35 36 37 public void init (String [] argv)throws Exception38 {39 int port = PORT_NUMBER;40 41 if (argv.length > 0) { 42 port = Integer.parseInt (argv [0]);43 }44 45 System.out.println ("Listening on port " + port);46 47 // 分配一個ServerSocketChannel48 serverChannel = ServerSocketChannel.open();49 // 從ServerSocketChannel裡獲得一個對應的Socket50 serverSocket = serverChannel.socket();51 // 產生一個Selector52 selector = Selector.open();53 54 // 把Socket綁定到連接埠上55 serverSocket.bind (new InetSocketAddress (port));56 //serverChannel為非bolck57 serverChannel.configureBlocking (false);58 59 // 通過Selector註冊ServerSocetChannel60 serverChannel.register (selector, SelectionKey.OP_ACCEPT); 61 62 }63 64 public void startWork()throws Exception 65 66 {67 while (true) {68 69 int n = selector.select();//獲得IO準備就緒的channel數量70 71 if (n == 0) {72 continue; // 沒有channel準備就緒,繼續執行73 }74 75 // 用一個iterator返回Selector的selectedkeys76 Iterator it = selector.selectedKeys().iterator();77 78 // 處理每一個SelectionKey79 while (it.hasNext()) {80 SelectionKey key = (SelectionKey) it.next();8182 // 判斷是否有新的串連到達83 if (key.isAcceptable()) {84 //返回SelectionKey的ServerSocketChannel85 ServerSocketChannel server =(ServerSocketChannel) key.channel();86 SocketChannel channel = server.accept();87 88 registerChannel (selector, channel,89 SelectionKey.OP_READ);90 91 doWork (channel);92 }93 94 // 判斷是否有資料在此channel裡需要讀取95 if (key.isReadable()) {96 97 processData (key);98 99 }100 101 //刪除 selectedkeys102 it.remove();103 }104 }105 }106 protected void registerChannel (Selector selector,107 SelectableChannel channel, int ops)108 throws Exception109 { 110 if (channel == null) {111 return; 112 }113 114 115 channel.configureBlocking (false);116 117 channel.register (selector, ops);118 }119 120 //處理接收的資料121 protected void processData (SelectionKey key)122 throws Exception123 {124 125 126 SocketChannel socketChannel = (SocketChannel) key.channel();127 int count;128 129 buffer.clear(); // 清空buffer130 131 // 讀取所有的資料132 while ((count = socketChannel.read (buffer)) > 0) {133 buffer.flip(); 134135 // send the data, don't assume it goes all at once136 while (buffer.hasRemaining())137 {138 //如果收到斷行符號鍵,則在返回的字元前增加[echo]$字樣139 if(buffer.get()==(char)13)140 {141 buffer.clear();142 buffer.put("[echo]$".getBytes());143 buffer.flip();144 145 }146 socketChannel.write (buffer);//在Socket裡寫資料147 }148 149 buffer.clear(); // 清空buffer150 }151 152 if (count < 0) {153 // count<0,說明已經讀取完畢154 socketChannel.close(); 155 }156 }157 158 159 private void doWork (SocketChannel channel)throws Exception160 {161 buffer.clear();162 buffer.put ("Hello,I am working,please input some thing,and i will echo to you![echo] $".getBytes());163 buffer.flip();164 channel.write (buffer);165 }166 167 } |
使用:運行此程式,然後在控制台輸入命令telnet localhost 23。
【程式輸出結果】12-1所示。
圖12-1 輸出結果
【程式註解】
關於程式的解釋已經包含在程式裡面了,在這裡我們總結以下使用ServerSocket Channel開發服務端程式的過程:
(1)分配一個ServerSocketChannel。
(2)從ServerSocketChannel裡獲得一個對應的ServerSocket。
(3)產生一個Selector執行個體。
(4)把ServerSocket綁定到連接埠上。
(5)設定ServerSocketChannel為非block模式(可選)。
(6)在Selector裡註冊ServerSocetChannel。
(7)用一個無限迴圈語句始終查看Selector裡是否有IO準備就緒的channel。如果有,就執行對應的處理,如果沒有,繼續迴圈。
小 結
在本章我們主要介紹了Java中的網路編程。Java一開始就是一種網路程式設計語言,到後來才應用到各個方面,所以在Java中進行網路編程遠比在C/C++中方便。
我們介紹了幾個在網路編程中很重要的類,如InetAddress、URL、URLConnection、Socket、 ServerSocket、DatagramSocket、DatagramPacket、MulticastSocket等。這些類包含了進行基本網路編程的所有內容。要熟練地應用這些類,關鍵還是要多多練習。
基於通訊端的編程基本上是客戶/伺服器模式,我們具體介紹了編寫這種模式的步驟。在執行個體方面,我們給出了一個基於TCP的通訊端客戶/伺服器程式,與此相對應,還給出了基於UDP的客戶/伺服器程式。兩者的模式是很相似的,其實這也就是編寫客戶/伺服器程式的一般模式。 (T111)