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