Java代碼

- 晚上學習了下Java 的 NIO Socket程式設計,寫了下面這個小程式,包括伺服器端與用戶端。實作的功能為用戶端向伺服器端發送随即數目的消息,伺服器端一條一條的回應。消息内容儲存在talks.properties檔案中,内容為:
- Hi=Hi
- Bye=Bye
- 床前明月光=疑是地上霜
- 舉頭望明月=低頭思故鄉
- 少小離家老大回=鄉音無改鬓毛衰
- 天王蓋地虎=寶塔鎮河妖
- 我是甲=我是乙
- 我是用戶端=我是伺服器
- 我是周星馳=我是周潤發
- 用戶端會随即發送“=”左邊的消息,伺服器端會回應用戶端“=”右邊的消息。如果用戶端想斷開連接配接,會向伺服器發送一個"Bye",然後伺服器會回應一個"Bye“。收到伺服器端的"Bye"後,用戶端會斷開連接配接。
- 當然,java的properties檔案不接受中文内容,你需要native2ascii一下。talks.properties 的實際檔案内容為:
- Hi=Hi
- Bye=Bye
- \u5E8A\u524D\u660E\u6708\u5149=\u7591\u662F\u5730\u4E0A\u971C
- \u4E3E\u5934\u671B\u660E\u6708=\u4F4E\u5934\u601D\u6545\u4E61
- \u5C11\u5C0F\u79BB\u5BB6\u8001\u5927\u56DE=\u4E61\u97F3\u65E0\u6539\u9B13\u6BDB\u8870
- \u5929\u738B\u76D6\u5730\u864E=\u5B9D\u5854\u9547\u6CB3\u5996
- \u6211\u662F\u7532=\u6211\u662F\u4E59
- \u6211\u662F\u5BA2\u6237\u7AEF=\u6211\u662F\u670D\u52A1\u5668
- \u6211\u662F\u5468\u661F\u9A70=\u6211\u662F\u5468\u6DA6\u53D1
- 看下伺服器端的代碼。此例中的伺服器端隻有一個主線程,用于selector操作,并處理多個用戶端的消息。在正常的socket程式設計中,每個用戶端都需要單獨開一個線程,效率比較低。代碼為:
- package helloweenpad;
- import java.io.FileInputStream;
- import java.net.InetSocketAddress;
- import java.net.Socket;
- import java.nio.ByteBuffer;
- import java.nio.CharBuffer;
- 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;
- import java.nio.charset.CharsetDecoder;
- import java.nio.charset.CharsetEncoder;
- import java.util.Iterator;
- import java.util.Properties;
- public class MyFirstNIOServer {
- public static final int PORT = 12315;
- protected Selector selector;
- protected Charset charset = Charset.forName("UTF-8");
- protected CharsetEncoder charsetEncoder = charset.newEncoder();
- protected CharsetDecoder charsetDecoder = charset.newDecoder();
- protected Properties talks = new Properties();
- int clientCount;
- public MyFirstNIOServer() throws Exception {
- talks.load(new FileInputStream("E:\\talk.properties"));
- selector = Selector.open();
- ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
- serverSocketChannel.socket().bind(new InetSocketAddress(PORT)); // port
- serverSocketChannel.configureBlocking(false);
- serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// register
- p("Server localhost:" + PORT + " started. waiting for clients. ");
- while (true) {
- // selector 線程。select() 會阻塞,直到有用戶端連接配接,或者有消息讀入
- selector.select();
- Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
- while (iterator.hasNext()) {
- SelectionKey selectionKey = iterator.next();
- iterator.remove(); // 删除此消息
- // 并在目前線程内處理。(為了高效,一般會在另一個線程中處理此消息,例如使用線程池等)
- handleSelectionKey(selectionKey);
- }
- }
- }
- public void handleSelectionKey(SelectionKey selectionKey) throws Exception {
- if (selectionKey.isAcceptable()) {
- // 有用戶端進來
- clientCount++;
- ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
- SocketChannel socketChannel = serverSocketChannel.accept();
- socketChannel.configureBlocking(false);
- Socket socket = socketChannel.socket();
- // 立即注冊一個 OP_READ 的SelectionKey, 接收用戶端的消息
- SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ);
- key.attach("第 " + clientCount + " 個用戶端 [" + socket.getRemoteSocketAddress() + "]: ");
- p(key.attachment() + "\t[connected] =========================================");
- } else if (selectionKey.isReadable()) {
- // 有消息進來
- ByteBuffer byteBuffer = ByteBuffer.allocate(100);
- SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
- try {
- int len = socketChannel.read(byteBuffer);
- // 如果len>0,表示有輸入。如果len==0, 表示輸入結束。需要關閉 socketChannel
- if (len > 0) {
- byteBuffer.flip();
- String msg = charsetDecoder.decode(byteBuffer).toString();
- // 根據用戶端的消息,查找到對應的輸出
- String newMsg = talks.getProperty(msg);
- if (newMsg == null)
- newMsg = "Sorry? I don't understand your message. ";
- // UTF-8 格式輸出到用戶端,并輸出一個'n'
- socketChannel.write(charsetEncoder.encode(CharBuffer.wrap(newMsg + "\n")));
- p(selectionKey.attachment() + "\t[recieved]: " + msg + " ----->\t[send]: " + newMsg);
- } else {
- // 輸入結束,關閉 socketChannel
- p(selectionKey.attachment() + "read finished. close socketChannel. ");
- socketChannel.close();
- }
- } catch (Exception e) {
- // 如果read抛出異常,表示連接配接異常中斷,需要關閉 socketChannel
- e.printStackTrace();
- p(selectionKey.attachment() + "socket closed? ");
- socketChannel.close();
- }
- } else if (selectionKey.isWritable()) {
- p(selectionKey.attachment() + "TODO: isWritable() ???????????????????????????? ");
- } else if (selectionKey.isConnectable()) {
- p(selectionKey.attachment() + "TODO: isConnectable() ????????????????????????? ");
- } else {
- p(selectionKey.attachment() + "TODO: else. ");
- }
- }
- public static void p(Object object) {
- System.out.println(object);
- }
- public static void main(String[] args) throws Exception {
- new MyFirstNIOServer();
- }
- }
- 再看下用戶端代碼。這個用戶端使用了正常的socket程式設計,沒有使用NIO。是否使用NIO對另一方是透明的,對方看不見,也不關心。無論使用NIO還是使用正常socket,效果都是一樣的,隻是NIO的效率要高一些。代碼為:
- package helloweenpad;
- import java.io.BufferedReader;
- import java.io.FileInputStream;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.net.Socket;
- import java.util.Properties;
- import java.util.Random;
- public class MyFirstNIOClientTest extends Thread {
- public static final String HOST = "localhost";
- public static final int PORT = 12315;
- boolean exist = false;
- Properties talks = new Properties();
- Random random = new Random();
- String[] keys;
- int messageCount = 0;
- public void run() {
- try {
- // 對話内容
- talks.load(new FileInputStream("E:\\talk.properties"));
- // 用戶端發送 "=" 左邊的内容
- keys = new String[talks.size()];
- talks.keySet().toArray(keys);
- Socket socket = new Socket(HOST, PORT);
- OutputStream ous = socket.getOutputStream();
- InputStream ins = socket.getInputStream();
- // 接收線程,接收伺服器的回應
- RecieverThread reciever = new RecieverThread(ins);
- reciever.start();
- while (!exist) {
- messageCount++;
- // 選擇一個随機消息
- String msg = chooseMessage();
- synchronized (ins) {
- // 發送給伺服器端
- ous.write(msg.getBytes("UTF-8"));
- System.out.println("[send]\t" + messageCount + ": " + msg);
- // 然後等待接收線程
- ins.wait();
- }
- if (msg.equals("Bye")) {
- break;
- }
- }
- ins.close();
- ous.close();
- socket.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- public String chooseMessage() {
- int index = random.nextInt(keys.length);
- String msg = keys[index];
- // 如果 10 次就選中 Bye,則重新選擇,為了使對話内容多一些
- if (messageCount < 10 && msg.equalsIgnoreCase("Bye")) {
- return chooseMessage();
- }
- return msg;
- }
- // 接收線程
- class RecieverThread extends Thread {
- private InputStream ins;
- public RecieverThread(InputStream ins) {
- this.ins = ins;
- }
- @Override
- public void run() {
- try {
- String line = null;
- BufferedReader r = new BufferedReader(new InputStreamReader(
- ins, "UTF-8"));
- // readLine()會阻塞,直到伺服器輸出一個 '\n'
- while ((line = r.readLine()) != null) {
- System.out.println("[Recieved]: " + line);
- synchronized (ins) {
- // 接收到伺服器的消息,通知下主線程
- ins.notify();
- }
- if (line.trim().equals("Bye")) {
- exist = true;
- break;
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- public static void main(String[] args) throws Exception {
- // 開三個用戶端線程
- for (int i = 0; i < 3; i++) {
- try {
- new MyFirstNIOClientTest().start();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
- =============================================================================
- 伺服器端的輸出:
- Server localhost:12315 started. waiting for clients.
- 第 1 個用戶端 [/127.0.0.1:1890]: [connected] =========================================
- 第 2 個用戶端 [/127.0.0.1:1865]: [connected] =========================================
- 第 3 個用戶端 [/127.0.0.1:1866]: [connected] =========================================
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 我是周星馳 -----> [send]: 我是周潤發
- 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 少小離家老大回 -----> [send]: 鄉音無改鬓毛衰
- 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
- 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 我是用戶端 -----> [send]: 我是伺服器
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉
- 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 我是甲 -----> [send]: 我是乙
- 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 我是周星馳 -----> [send]: 我是周潤發
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
- 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: Hi -----> [send]: Hi
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 少小離家老大回 -----> [send]: 鄉音無改鬓毛衰
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 我是甲 -----> [send]: 我是乙
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
- 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 我是用戶端 -----> [send]: 我是伺服器
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: Hi -----> [send]: Hi
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: Hi -----> [send]: Hi
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: 我是周星馳 -----> [send]: 我是周潤發
- 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: 我是周星馳 -----> [send]: 我是周潤發
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 舉頭望明月 -----> [send]: 低頭思故鄉
- 第 2 個用戶端 [/127.0.0.1:1865]: [recieved]: Bye -----> [send]: Bye
- 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: Hi -----> [send]: Hi
- 第 2 個用戶端 [/127.0.0.1:1865]: read finished. close socketChannel.
- 第 1 個用戶端 [/127.0.0.1:1890]: [recieved]: Bye -----> [send]: Bye
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 我是用戶端 -----> [send]: 我是伺服器
- 第 1 個用戶端 [/127.0.0.1:1890]: read finished. close socketChannel.
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: 我是用戶端 -----> [send]: 我是伺服器
- 第 3 個用戶端 [/127.0.0.1:1866]: [recieved]: Bye -----> [send]: Bye
- 第 3 個用戶端 [/127.0.0.1:1866]: read finished. close socketChannel.
- 用戶端的輸出:
- [send] 1: 我是周星馳
- [send] 1: 少小離家老大回
- [Recieved]: 鄉音無改鬓毛衰
- [send] 2: 床前明月光
- [Recieved]: 疑是地上霜
- [send] 3: 我是用戶端
- [Recieved]: 我是伺服器
- [Recieved]: 疑是地上霜
- [Recieved]: 我是周潤發
- [Recieved]: 低頭思故鄉
- [send] 2: 舉頭望明月
- [Recieved]: 疑是地上霜
- [send] 3: 床前明月光
- [send] 1: 床前明月光
- [send] 2: 舉頭望明月
- [Recieved]: 低頭思故鄉
- [Recieved]: 我是乙
- [send] 4: 我是甲
- [Recieved]: 我是周潤發
- [send] 5: 我是周星馳
- [send] 3: 床前明月光
- [send] 6: 床前明月光
- [send] 4: 舉頭望明月
- [Recieved]: 低頭思故鄉
- [send] 5: 床前明月光
- [Recieved]: 疑是地上霜
- [Recieved]: 疑是地上霜
- [Recieved]: 疑是地上霜
- [Recieved]: Hi
- [send] 4: Hi
- [Recieved]: 低頭思故鄉
- [send] 5: 舉頭望明月
- [Recieved]: 低頭思故鄉
- [Recieved]: 低頭思故鄉
- [send] 6: 舉頭望明月
- [send] 7: 少小離家老大回
- [s