天天看點

Java NIO socket程式設計執行個體 (轉)

Java代碼  

Java NIO socket程式設計執行個體 (轉)
  1. 晚上學習了下Java 的 NIO Socket程式設計,寫了下面這個小程式,包括伺服器端與用戶端。實作的功能為用戶端向伺服器端發送随即數目的消息,伺服器端一條一條的回應。消息内容儲存在talks.properties檔案中,内容為:  
  2. Hi=Hi  
  3. Bye=Bye  
  4. 床前明月光=疑是地上霜  
  5. 舉頭望明月=低頭思故鄉  
  6. 少小離家老大回=鄉音無改鬓毛衰  
  7. 天王蓋地虎=寶塔鎮河妖  
  8. 我是甲=我是乙  
  9. 我是用戶端=我是伺服器  
  10. 我是周星馳=我是周潤發  
  11. 用戶端會随即發送“=”左邊的消息,伺服器端會回應用戶端“=”右邊的消息。如果用戶端想斷開連接配接,會向伺服器發送一個"Bye",然後伺服器會回應一個"Bye“。收到伺服器端的"Bye"後,用戶端會斷開連接配接。  
  12. 當然,java的properties檔案不接受中文内容,你需要native2ascii一下。talks.properties 的實際檔案内容為:  
  13. Hi=Hi  
  14. Bye=Bye  
  15. \u5E8A\u524D\u660E\u6708\u5149=\u7591\u662F\u5730\u4E0A\u971C  
  16. \u4E3E\u5934\u671B\u660E\u6708=\u4F4E\u5934\u601D\u6545\u4E61  
  17. \u5C11\u5C0F\u79BB\u5BB6\u8001\u5927\u56DE=\u4E61\u97F3\u65E0\u6539\u9B13\u6BDB\u8870  
  18. \u5929\u738B\u76D6\u5730\u864E=\u5B9D\u5854\u9547\u6CB3\u5996  
  19. \u6211\u662F\u7532=\u6211\u662F\u4E59  
  20. \u6211\u662F\u5BA2\u6237\u7AEF=\u6211\u662F\u670D\u52A1\u5668  
  21. \u6211\u662F\u5468\u661F\u9A70=\u6211\u662F\u5468\u6DA6\u53D1  
  22. 看下伺服器端的代碼。此例中的伺服器端隻有一個主線程,用于selector操作,并處理多個用戶端的消息。在正常的socket程式設計中,每個用戶端都需要單獨開一個線程,效率比較低。代碼為:  
  23. package helloweenpad;  
  24. import java.io.FileInputStream;  
  25. import java.net.InetSocketAddress;  
  26. import java.net.Socket;  
  27. import java.nio.ByteBuffer;  
  28. import java.nio.CharBuffer;  
  29. import java.nio.channels.SelectionKey;  
  30. import java.nio.channels.Selector;  
  31. import java.nio.channels.ServerSocketChannel;  
  32. import java.nio.channels.SocketChannel;  
  33. import java.nio.charset.Charset;  
  34. import java.nio.charset.CharsetDecoder;  
  35. import java.nio.charset.CharsetEncoder;  
  36. import java.util.Iterator;  
  37. import java.util.Properties;  
  38. public class MyFirstNIOServer {  
  39. public static final int PORT = 12315;  
  40. protected Selector selector;  
  41. protected Charset charset = Charset.forName("UTF-8");  
  42. protected CharsetEncoder charsetEncoder = charset.newEncoder();  
  43. protected CharsetDecoder charsetDecoder = charset.newDecoder();  
  44. protected Properties talks = new Properties();  
  45. int clientCount;  
  46. public MyFirstNIOServer() throws Exception {  
  47. talks.load(new FileInputStream("E:\\talk.properties"));  
  48. selector = Selector.open();  
  49. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
  50. serverSocketChannel.socket().bind(new InetSocketAddress(PORT)); // port  
  51. serverSocketChannel.configureBlocking(false);  
  52. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// register  
  53. p("Server localhost:" + PORT + " started. waiting for clients. ");  
  54. while (true) {  
  55.    // selector 線程。select() 會阻塞,直到有用戶端連接配接,或者有消息讀入  
  56.    selector.select();  
  57.    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();  
  58.    while (iterator.hasNext()) {  
  59.     SelectionKey selectionKey = iterator.next();  
  60.     iterator.remove(); // 删除此消息  
  61.     // 并在目前線程内處理。(為了高效,一般會在另一個線程中處理此消息,例如使用線程池等)  
  62.     handleSelectionKey(selectionKey);  
  63.    }  
  64. }  
  65. }  
  66. public void handleSelectionKey(SelectionKey selectionKey) throws Exception {  
  67. if (selectionKey.isAcceptable()) {  
  68.    // 有用戶端進來  
  69.    clientCount++;  
  70.    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();  
  71.    SocketChannel socketChannel = serverSocketChannel.accept();  
  72.    socketChannel.configureBlocking(false);  
  73.    Socket socket = socketChannel.socket();  
  74.    // 立即注冊一個 OP_READ 的SelectionKey, 接收用戶端的消息  
  75.    SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ);  
  76.    key.attach("第 " + clientCount + " 個用戶端 [" + socket.getRemoteSocketAddress() + "]: ");  
  77.    p(key.attachment() + "\t[connected] =========================================");  
  78. } else if (selectionKey.isReadable()) {  
  79.    // 有消息進來  
  80.    ByteBuffer byteBuffer = ByteBuffer.allocate(100);  
  81.    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();  
  82.    try {  
  83.     int len = socketChannel.read(byteBuffer);  
  84.     // 如果len>0,表示有輸入。如果len==0, 表示輸入結束。需要關閉 socketChannel  
  85.     if (len > 0) {  
  86.      byteBuffer.flip();  
  87.      String msg = charsetDecoder.decode(byteBuffer).toString();  
  88.      // 根據用戶端的消息,查找到對應的輸出  
  89.      String newMsg = talks.getProperty(msg);  
  90.      if (newMsg == null)  
  91.       newMsg = "Sorry? I don't understand your message. ";  
  92.      // UTF-8 格式輸出到用戶端,并輸出一個'n'  
  93.      socketChannel.write(charsetEncoder.encode(CharBuffer.wrap(newMsg + "\n")));  
  94.      p(selectionKey.attachment() + "\t[recieved]: " + msg + " ----->\t[send]: " + newMsg);  
  95.     } else {  
  96.      // 輸入結束,關閉 socketChannel  
  97.      p(selectionKey.attachment() + "read finished. close socketChannel. ");  
  98.      socketChannel.close();  
  99.     }  
  100.    } catch (Exception e) {  
  101.     // 如果read抛出異常,表示連接配接異常中斷,需要關閉 socketChannel  
  102.     e.printStackTrace();  
  103.     p(selectionKey.attachment() + "socket closed? ");  
  104.     socketChannel.close();  
  105.    }  
  106. } else if (selectionKey.isWritable()) {  
  107.    p(selectionKey.attachment() + "TODO: isWritable() ???????????????????????????? ");  
  108. } else if (selectionKey.isConnectable()) {  
  109.    p(selectionKey.attachment() + "TODO: isConnectable() ????????????????????????? ");  
  110. } else {  
  111.    p(selectionKey.attachment() + "TODO: else. ");  
  112. }  
  113. }  
  114. public static void p(Object object) {  
  115. System.out.println(object);  
  116. }  
  117. public static void main(String[] args) throws Exception {  
  118. new MyFirstNIOServer();  
  119. }  
  120. }  
  121. 再看下用戶端代碼。這個用戶端使用了正常的socket程式設計,沒有使用NIO。是否使用NIO對另一方是透明的,對方看不見,也不關心。無論使用NIO還是使用正常socket,效果都是一樣的,隻是NIO的效率要高一些。代碼為:  
  122. package helloweenpad;  
  123. import java.io.BufferedReader;  
  124. import java.io.FileInputStream;  
  125. import java.io.InputStream;  
  126. import java.io.InputStreamReader;  
  127. import java.io.OutputStream;  
  128. import java.net.Socket;  
  129. import java.util.Properties;  
  130. import java.util.Random;  
  131. public class MyFirstNIOClientTest extends Thread {  
  132. public static final String HOST = "localhost";  
  133. public static final int PORT = 12315;  
  134. boolean exist = false;  
  135. Properties talks = new Properties();  
  136. Random random = new Random();  
  137. String[] keys;  
  138. int messageCount = 0;  
  139. public void run() {  
  140. try {  
  141.    // 對話内容  
  142.    talks.load(new FileInputStream("E:\\talk.properties"));  
  143.    // 用戶端發送 "=" 左邊的内容  
  144.    keys = new String[talks.size()];  
  145.    talks.keySet().toArray(keys);  
  146.    Socket socket = new Socket(HOST, PORT);  
  147.    OutputStream ous = socket.getOutputStream();  
  148.    InputStream ins = socket.getInputStream();  
  149.    // 接收線程,接收伺服器的回應  
  150.    RecieverThread reciever = new RecieverThread(ins);  
  151.    reciever.start();  
  152.    while (!exist) {  
  153.     messageCount++;  
  154.     // 選擇一個随機消息  
  155.     String msg = chooseMessage();  
  156.     synchronized (ins) {  
  157.      // 發送給伺服器端  
  158.      ous.write(msg.getBytes("UTF-8"));  
  159.      System.out.println("[send]\t" + messageCount + ": " + msg);  
  160.      // 然後等待接收線程  
  161.      ins.wait();  
  162.     }  
  163.     if (msg.equals("Bye")) {  
  164.      break;  
  165.     }  
  166.    }  
  167.    ins.close();  
  168.    ous.close();  
  169.    socket.close();  
  170. } catch (Exception e) {  
  171.    e.printStackTrace();  
  172. }  
  173. }  
  174. public String chooseMessage() {  
  175. int index = random.nextInt(keys.length);  
  176. String msg = keys[index];  
  177. // 如果 10 次就選中 Bye,則重新選擇,為了使對話内容多一些  
  178. if (messageCount < 10 && msg.equalsIgnoreCase("Bye")) {  
  179.    return chooseMessage();  
  180. }  
  181. return msg;  
  182. }  
  183. // 接收線程  
  184. class RecieverThread extends Thread {  
  185. private InputStream ins;  
  186. public RecieverThread(InputStream ins) {  
  187.    this.ins = ins;  
  188. }  
  189. @Override  
  190. public void run() {  
  191.    try {  
  192.     String line = null;  
  193.     BufferedReader r = new BufferedReader(new InputStreamReader(  
  194.       ins, "UTF-8"));  
  195.     // readLine()會阻塞,直到伺服器輸出一個 '\n'  
  196.     while ((line = r.readLine()) != null) {  
  197.      System.out.println("[Recieved]: " + line);  
  198.      synchronized (ins) {  
  199.       // 接收到伺服器的消息,通知下主線程  
  200.       ins.notify();  
  201.      }  
  202.      if (line.trim().equals("Bye")) {  
  203.       exist = true;  
  204.       break;  
  205.      }  
  206.     }  
  207.    } catch (Exception e) {  
  208.     e.printStackTrace();  
  209.    }  
  210. }  
  211. }  
  212. public static void main(String[] args) throws Exception {  
  213. // 開三個用戶端線程  
  214. for (int i = 0; i < 3; i++) {  
  215.    try {  
  216.     new MyFirstNIOClientTest().start();  
  217.    } catch (Exception e) {  
  218.     e.printStackTrace();  
  219.    }  
  220. }  
  221. }  
  222. }  
  223. =============================================================================  
  224. 伺服器端的輸出:  
  225. Server localhost:12315 started. waiting for clients.  
  226. 第 1 個用戶端 [/127.0.0.1:1890]: [connected] =========================================  
  227. 第 2 個用戶端 [/127.0.0.1:1865]: [connected] =========================================  
  228. 第 3 個用戶端 [/127.0.0.1:1866]: [connected] =========================================  
  229. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  230. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 我是周星馳 -----> [send]: 我是周潤發  
  231. 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 少小離家老大回 -----> [send]: 鄉音無改鬓毛衰  
  232. 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  233. 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 我是用戶端 -----> [send]: 我是伺服器  
  234. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉  
  235. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  236. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉  
  237. 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 我是甲 -----> [send]: 我是乙  
  238. 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 我是周星馳 -----> [send]: 我是周潤發  
  239. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉  
  240. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  241. 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  242. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  243. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: Hi -----> [send]: Hi  
  244. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉  
  245. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉  
  246. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 少小離家老大回 -----> [send]: 鄉音無改鬓毛衰  
  247. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 我是甲 -----> [send]: 我是乙  
  248. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  249. 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 我是用戶端 -----> [send]: 我是伺服器  
  250. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: Hi -----> [send]: Hi  
  251. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: Hi -----> [send]: Hi  
  252. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 我是周星馳 -----> [send]: 我是周潤發  
  253. 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 我是周星馳 -----> [send]: 我是周潤發  
  254. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉  
  255. 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: Bye -----> [send]: Bye  
  256. 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: Hi -----> [send]: Hi  
  257. 第 2 個用戶端 [/127.0.0.1:1865]: read finished. close socketChannel.  
  258. 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: Bye -----> [send]: Bye  
  259. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 我是用戶端 -----> [send]: 我是伺服器  
  260. 第 1 個用戶端 [/127.0.0.1:1890]: read finished. close socketChannel.  
  261. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 我是用戶端 -----> [send]: 我是伺服器  
  262. 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: Bye -----> [send]: Bye  
  263. 第 3 個用戶端 [/127.0.0.1:1866]: read finished. close socketChannel.  
  264. 用戶端的輸出:  
  265. [send] 1: 我是周星馳  
  266. [send] 1: 少小離家老大回  
  267. [Recieved]: 鄉音無改鬓毛衰  
  268. [send] 2: 床前明月光  
  269. [Recieved]: 疑是地上霜  
  270. [send] 3: 我是用戶端  
  271. [Recieved]: 我是伺服器  
  272. [Recieved]: 疑是地上霜  
  273. [Recieved]: 我是周潤發  
  274. [Recieved]: 低頭思故鄉  
  275. [send] 2: 舉頭望明月  
  276. [Recieved]: 疑是地上霜  
  277. [send] 3: 床前明月光  
  278. [send] 1: 床前明月光  
  279. [send] 2: 舉頭望明月  
  280. [Recieved]: 低頭思故鄉  
  281. [Recieved]: 我是乙  
  282. [send] 4: 我是甲  
  283. [Recieved]: 我是周潤發  
  284. [send] 5: 我是周星馳  
  285. [send] 3: 床前明月光  
  286. [send] 6: 床前明月光  
  287. [send] 4: 舉頭望明月  
  288. [Recieved]: 低頭思故鄉  
  289. [send] 5: 床前明月光  
  290. [Recieved]: 疑是地上霜  
  291. [Recieved]: 疑是地上霜  
  292. [Recieved]: 疑是地上霜  
  293. [Recieved]: Hi  
  294. [send] 4: Hi  
  295. [Recieved]: 低頭思故鄉  
  296. [send] 5: 舉頭望明月  
  297. [Recieved]: 低頭思故鄉  
  298. [Recieved]: 低頭思故鄉  
  299. [send] 6: 舉頭望明月  
  300. [send] 7: 少小離家老大回  
  301. [s