Java NIO socket编程实例 (转)

Java代码   收藏代码
  1. 晚上学习了下Java 的 NIO Socket编程,写了下面这个小程序,包括服务器端与客户端。实现的功能为客户端向服务器端发送随即数目的消息,服务器端一条一条的回应。消息内容保存在talks.properties文件中,内容为:  
  2.   
  3. Hi=Hi  
  4. Bye=Bye  
  5. 床前明月光=疑是地上霜  
  6. 举头望明月=低头思故乡  
  7. 少小离家老大回=乡音无改鬓毛衰  
  8. 天王盖地虎=宝塔镇河妖  
  9. 我是甲=我是乙  
  10. 我是客户端=我是服务器  
  11. 我是周星驰=我是周润发  
  12.   
  13. 客户端会随即发送“=”左边的消息,服务器端会回应客户端“=”右边的消息。如果客户端想断开连接,会向服务器发送一个"Bye",然后服务器会回应一个"Bye“。收到服务器端的"Bye"后,客户端会断开连接。  
  14.   
  15. 当然,java的properties文件不接受中文内容,你需要native2ascii一下。talks.properties 的实际文件内容为:  
  16.   
  17. Hi=Hi  
  18. Bye=Bye  
  19. \u5E8A\u524D\u660E\u6708\u5149=\u7591\u662F\u5730\u4E0A\u971C  
  20. \u4E3E\u5934\u671B\u660E\u6708=\u4F4E\u5934\u601D\u6545\u4E61  
  21. \u5C11\u5C0F\u79BB\u5BB6\u8001\u5927\u56DE=\u4E61\u97F3\u65E0\u6539\u9B13\u6BDB\u8870  
  22. \u5929\u738B\u76D6\u5730\u864E=\u5B9D\u5854\u9547\u6CB3\u5996  
  23. \u6211\u662F\u7532=\u6211\u662F\u4E59  
  24. \u6211\u662F\u5BA2\u6237\u7AEF=\u6211\u662F\u670D\u52A1\u5668  
  25. \u6211\u662F\u5468\u661F\u9A70=\u6211\u662F\u5468\u6DA6\u53D1  
  26.   
  27.   
  28. 看下服务器端的代码。此例中的服务器端只有一个主线程,用于selector操作,并处理多个客户端的消息。在常规的socket编程中,每个客户端都需要单独开一个线程,效率比较低。代码为:  
  29.   
  30. package helloweenpad;  
  31.   
  32. import java.io.FileInputStream;  
  33. import java.net.InetSocketAddress;  
  34. import java.net.Socket;  
  35. import java.nio.ByteBuffer;  
  36. import java.nio.CharBuffer;  
  37. import java.nio.channels.SelectionKey;  
  38. import java.nio.channels.Selector;  
  39. import java.nio.channels.ServerSocketChannel;  
  40. import java.nio.channels.SocketChannel;  
  41. import java.nio.charset.Charset;  
  42. import java.nio.charset.CharsetDecoder;  
  43. import java.nio.charset.CharsetEncoder;  
  44. import java.util.Iterator;  
  45. import java.util.Properties;  
  46.   
  47. public class MyFirstNIOServer {  
  48.   
  49. public static final int PORT = 12315;  
  50.   
  51. protected Selector selector;  
  52. protected Charset charset = Charset.forName("UTF-8");  
  53. protected CharsetEncoder charsetEncoder = charset.newEncoder();  
  54. protected CharsetDecoder charsetDecoder = charset.newDecoder();  
  55.   
  56. protected Properties talks = new Properties();  
  57.   
  58. int clientCount;  
  59.   
  60. public MyFirstNIOServer() throws Exception {  
  61.   
  62. talks.load(new FileInputStream("E:\\talk.properties"));  
  63.   
  64. selector = Selector.open();  
  65.   
  66. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
  67. serverSocketChannel.socket().bind(new InetSocketAddress(PORT)); // port  
  68. serverSocketChannel.configureBlocking(false);  
  69. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// register  
  70.   
  71. p("Server localhost:" + PORT + " started. waiting for clients. ");  
  72.   
  73. while (true) {  
  74.    // selector 线程。select() 会阻塞,直到有客户端连接,或者有消息读入  
  75.    selector.select();  
  76.    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();  
  77.    while (iterator.hasNext()) {  
  78.   
  79.     SelectionKey selectionKey = iterator.next();  
  80.     iterator.remove(); // 删除此消息  
  81.   
  82.     // 并在当前线程内处理。(为了高效,一般会在另一个线程中处理此消息,例如使用线程池等)  
  83.     handleSelectionKey(selectionKey);  
  84.    }  
  85. }  
  86.   
  87. }  
  88.   
  89. public void handleSelectionKey(SelectionKey selectionKey) throws Exception {  
  90.   
  91. if (selectionKey.isAcceptable()) {  
  92.   
  93.    // 有客户端进来  
  94.    clientCount++;  
  95.   
  96.    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();  
  97.    SocketChannel socketChannel = serverSocketChannel.accept();  
  98.    socketChannel.configureBlocking(false);  
  99.    Socket socket = socketChannel.socket();  
  100.   
  101.    // 立即注册一个 OP_READ 的SelectionKey, 接收客户端的消息  
  102.    SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ);  
  103.    key.attach("第 " + clientCount + " 个客户端 [" + socket.getRemoteSocketAddress() + "]: ");  
  104.   
  105.    p(key.attachment() + "\t[connected] =========================================");  
  106.   
  107. else if (selectionKey.isReadable()) {  
  108.   
  109.    // 有消息进来  
  110.   
  111.    ByteBuffer byteBuffer = ByteBuffer.allocate(100);  
  112.    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();  
  113.   
  114.    try {  
  115.     int len = socketChannel.read(byteBuffer);  
  116.   
  117.     // 如果len>0,表示有输入。如果len==0, 表示输入结束。需要关闭 socketChannel  
  118.     if (len > 0) {  
  119.   
  120.      byteBuffer.flip();  
  121.      String msg = charsetDecoder.decode(byteBuffer).toString();  
  122.   
  123.      // 根据客户端的消息,查找到对应的输出  
  124.      String newMsg = talks.getProperty(msg);  
  125.      if (newMsg == null)  
  126.       newMsg = "Sorry? I don't understand your message. ";  
  127.   
  128.      // UTF-8 格式输出到客户端,并输出一个'n'  
  129.   
  130.      socketChannel.write(charsetEncoder.encode(CharBuffer.wrap(newMsg + "\n")));  
  131.      p(selectionKey.attachment() + "\t[recieved]: " + msg + " ----->\t[send]: " + newMsg);  
  132.   
  133.     } else {  
  134.      // 输入结束,关闭 socketChannel  
  135.      p(selectionKey.attachment() + "read finished. close socketChannel. ");  
  136.      socketChannel.close();  
  137.     }  
  138.   
  139.    } catch (Exception e) {  
  140.   
  141.     // 如果read抛出异常,表示连接异常中断,需要关闭 socketChannel  
  142.     e.printStackTrace();  
  143.   
  144.     p(selectionKey.attachment() + "socket closed? ");  
  145.     socketChannel.close();  
  146.    }  
  147.   
  148. else if (selectionKey.isWritable()) {  
  149.    p(selectionKey.attachment() + "TODO: isWritable() ???????????????????????????? ");  
  150. else if (selectionKey.isConnectable()) {  
  151.    p(selectionKey.attachment() + "TODO: isConnectable() ????????????????????????? ");  
  152. else {  
  153.    p(selectionKey.attachment() + "TODO: else. ");  
  154. }  
  155.   
  156. }  
  157.   
  158. public static void p(Object object) {  
  159. System.out.println(object);  
  160. }  
  161.   
  162. public static void main(String[] args) throws Exception {  
  163. new MyFirstNIOServer();  
  164. }  
  165.   
  166. }  
  167.   
  168. 再看下客户端代码。这个客户端使用了常规的socket编程,没有使用NIO。是否使用NIO对另一方是透明的,对方看不见,也不关心。无论使用NIO还是使用常规socket,效果都是一样的,只是NIO的效率要高一些。代码为:  
  169.   
  170. package helloweenpad;  
  171.   
  172. import java.io.BufferedReader;  
  173. import java.io.FileInputStream;  
  174. import java.io.InputStream;  
  175. import java.io.InputStreamReader;  
  176. import java.io.OutputStream;  
  177. import java.net.Socket;  
  178. import java.util.Properties;  
  179. import java.util.Random;  
  180.   
  181. public class MyFirstNIOClientTest extends Thread {  
  182.   
  183. public static final String HOST = "localhost";  
  184. public static final int PORT = 12315;  
  185.   
  186. boolean exist = false;  
  187.   
  188. Properties talks = new Properties();  
  189. Random random = new Random();  
  190. String[] keys;  
  191.   
  192. int messageCount = 0;  
  193.   
  194. public void run() {  
  195.   
  196. try {  
  197.   
  198.    // 对话内容  
  199.    talks.load(new FileInputStream("E:\\talk.properties"));  
  200.   
  201.    // 客户端发送 "=" 左边的内容  
  202.    keys = new String[talks.size()];  
  203.    talks.keySet().toArray(keys);  
  204.   
  205.    Socket socket = new Socket(HOST, PORT);  
  206.   
  207.    OutputStream ous = socket.getOutputStream();  
  208.    InputStream ins = socket.getInputStream();  
  209.   
  210.    // 接收线程,接收服务器的回应  
  211.    RecieverThread reciever = new RecieverThread(ins);  
  212.    reciever.start();  
  213.   
  214.    while (!exist) {  
  215.   
  216.     messageCount++;  
  217.   
  218.     // 选择一个随机消息  
  219.     String msg = chooseMessage();  
  220.   
  221.     synchronized (ins) {  
  222.   
  223.      // 发送给服务器端  
  224.      ous.write(msg.getBytes("UTF-8"));  
  225.   
  226.      System.out.println("[send]\t" + messageCount + ": " + msg);  
  227.   
  228.      // 然后等待接收线程  
  229.      ins.wait();  
  230.     }  
  231.   
  232.     if (msg.equals("Bye")) {  
  233.      break;  
  234.     }  
  235.    }  
  236.   
  237.    ins.close();  
  238.    ous.close();  
  239.    socket.close();  
  240.   
  241. catch (Exception e) {  
  242.    e.printStackTrace();  
  243. }  
  244.   
  245. }  
  246.   
  247. public String chooseMessage() {  
  248.   
  249. int index = random.nextInt(keys.length);  
  250. String msg = keys[index];  
  251.   
  252. // 如果 10 次就选中 Bye,则重新选择,为了使对话内容多一些  
  253. if (messageCount < 10 && msg.equalsIgnoreCase("Bye")) {  
  254.    return chooseMessage();  
  255. }  
  256.   
  257. return msg;  
  258. }  
  259.   
  260. // 接收线程  
  261. class RecieverThread extends Thread {  
  262. private InputStream ins;  
  263.   
  264. public RecieverThread(InputStream ins) {  
  265.    this.ins = ins;  
  266. }  
  267.   
  268. @Override  
  269. public void run() {  
  270.   
  271.    try {  
  272.     String line = null;  
  273.   
  274.     BufferedReader r = new BufferedReader(new InputStreamReader(  
  275.       ins, "UTF-8"));  
  276.   
  277.     // readLine()会阻塞,直到服务器输出一个 '\n'  
  278.     while ((line = r.readLine()) != null) {  
  279.   
  280.      System.out.println("[Recieved]: " + line);  
  281.   
  282.      synchronized (ins) {  
  283.       // 接收到服务器的消息,通知下主线程  
  284.       ins.notify();  
  285.      }  
  286.      if (line.trim().equals("Bye")) {  
  287.       exist = true;  
  288.       break;  
  289.      }  
  290.     }  
  291.    } catch (Exception e) {  
  292.     e.printStackTrace();  
  293.    }  
  294. }  
  295.   
  296. }  
  297.   
  298. public static void main(String[] args) throws Exception {  
  299.   
  300. // 开三个客户端线程  
  301. for (int i = 0; i < 3; i++) {  
  302.    try {  
  303.     new MyFirstNIOClientTest().start();  
  304.    } catch (Exception e) {  
  305.     e.printStackTrace();  
  306.    }  
  307. }  
  308.   
  309. }  
  310. }  
  311.   
  312. =============================================================================  
  313.   
  314. 服务器端的输出:  
  315.   
  316. Server localhost:12315 started. waiting for clients.  
  317. 第 1 个客户端 [/127.0.0.1:1890]: [connected] =========================================  
  318. 第 2 个客户端 [/127.0.0.1:1865]: [connected] =========================================  
  319. 第 3 个客户端 [/127.0.0.1:1866]: [connected] =========================================  
  320. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  321. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是周星驰 -----> [send]: 我是周润发  
  322. 第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 少小离家老大回 -----> [send]: 乡音无改鬓毛衰  
  323. 第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  324. 第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是客户端 -----> [send]: 我是服务器  
  325. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 举头望明月 -----> [send]: 低头思故乡  
  326. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  327. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 举头望明月 -----> [send]: 低头思故乡  
  328. 第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是甲 -----> [send]: 我是乙  
  329. 第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是周星驰 -----> [send]: 我是周润发  
  330. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 举头望明月 -----> [send]: 低头思故乡  
  331. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  332. 第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  333. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  334. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: Hi -----> [send]: Hi  
  335. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 举头望明月 -----> [send]: 低头思故乡  
  336. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 举头望明月 -----> [send]: 低头思故乡  
  337. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 少小离家老大回 -----> [send]: 乡音无改鬓毛衰  
  338. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是甲 -----> [send]: 我是乙  
  339. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜  
  340. 第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是客户端 -----> [send]: 我是服务器  
  341. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: Hi -----> [send]: Hi  
  342. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: Hi -----> [send]: Hi  
  343. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 我是周星驰 -----> [send]: 我是周润发  
  344. 第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是周星驰 -----> [send]: 我是周润发  
  345. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 举头望明月 -----> [send]: 低头思故乡  
  346. 第 2 个客户端 [/127.0.0.1:1865]: [recieved]: Bye -----> [send]: Bye  
  347. 第 1 个客户端 [/127.0.0.1:1890]: [recieved]: Hi -----> [send]: Hi  
  348. 第 2 个客户端 [/127.0.0.1:1865]: read finished. close socketChannel.  
  349. 第 1 个客户端 [/127.0.0.1:1890]: [recieved]: Bye -----> [send]: Bye  
  350. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是客户端 -----> [send]: 我是服务器  
  351. 第 1 个客户端 [/127.0.0.1:1890]: read finished. close socketChannel.  
  352. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是客户端 -----> [send]: 我是服务器  
  353. 第 3 个客户端 [/127.0.0.1:1866]: [recieved]: Bye -----> [send]: Bye  
  354. 第 3 个客户端 [/127.0.0.1:1866]: read finished. close socketChannel.  
  355.   
  356. 客户端的输出:  
  357.   
  358. [send] 1: 我是周星驰  
  359. [send] 1: 少小离家老大回  
  360. [Recieved]: 乡音无改鬓毛衰  
  361. [send] 2: 床前明月光  
  362. [Recieved]: 疑是地上霜  
  363. [send] 3: 我是客户端  
  364. [Recieved]: 我是服务器  
  365. [Recieved]: 疑是地上霜  
  366. [Recieved]: 我是周润发  
  367. [Recieved]: 低头思故乡  
  368. [send] 2: 举头望明月  
  369. [Recieved]: 疑是地上霜  
  370. [send] 3: 床前明月光  
  371. [send] 1: 床前明月光  
  372. [send] 2: 举头望明月  
  373. [Recieved]: 低头思故乡  
  374. [Recieved]: 我是乙  
  375. [send] 4: 我是甲  
  376. [Recieved]: 我是周润发  
  377. [send] 5: 我是周星驰  
  378. [send] 3: 床前明月光  
  379. [send] 6: 床前明月光  
  380. [send] 4: 举头望明月  
  381. [Recieved]: 低头思故乡  
  382. [send] 5: 床前明月光  
  383. [Recieved]: 疑是地上霜  
  384. [Recieved]: 疑是地上霜  
  385. [Recieved]: 疑是地上霜  
  386. [Recieved]: Hi  
  387. [send] 4: Hi  
  388.   
  389. [Recieved]: 低头思故乡  
  390. [send] 5: 举头望明月  
  391. [Recieved]: 低头思故乡  
  392.   
  393.   
  394. [Recieved]: 低头思故乡  
  395. [send] 6: 举头望明月  
  396.   
  397.   
  398. [send] 7: 少小离家老大回  
  399.   
  400. [s  

猜你喜欢

转载自rainyear.iteye.com/blog/1736334