Client端
/**
* 《构建高性能的大型分布式Java应用》书中的示例代码, 版权所有:2008---2009
*/
package book.chapter1.tcpnio;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
/**
* 描述:基于java NIO实现的tcp client
*
* @author bluedavy
* 创建时间: 2008-12-2
*/
public class Client {
public static void main(String[] args) throws Exception{
Charset charset = Charset.forName("UTF-8");
int port = 9527;
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); // 设置为非阻塞模式
SocketAddress target =new InetSocketAddress("127.0.0.1", port);
socketChannel.connect(target); // 对于非阻塞模式,立即返回false,表示连接正在建立中
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT); // 向register注册该channel及其感兴趣的连接事件
BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));
while(true){ // 这里体现了NIO需要不停地去检测是否有感兴趣的事件到达这个特点,是与AIO最大的区别之一
if(socketChannel.isConnected()){
String command = systemIn.readLine();
socketChannel.write(charset.encode(command));
if(command==null || "quit".equalsIgnoreCase(command.trim())){
systemIn.close();
socketChannel.close();
selector.close();
System.out.println("Client quit!");
System.exit(0);
}
}
// 阻塞至有兴趣的IO事件发生,或达到超时(1000ms)时间,如果希望一直等至有兴趣的IO事件发生,可调用
// 无参数的select()方法,如果希望不阻塞直接返回目前是否有感兴趣的事件发生,可调用selectNow()方法
int nKeys = selector.select(1000);// 超时时间:1000ms
if (nKeys>0) {// select方法返回的nKeys是感兴趣的事件的个数
for (SelectionKey key : selector.selectedKeys()) {
if (key.isConnectable()) {// 可连接事件
SocketChannel sc = (SocketChannel) key.channel();
sc.configureBlocking(false);
// 注册感兴趣的IO读事件,通常不直接注册写事件,在socket发送缓冲区未满的情况下,一直是
// 可写的。如果注册了写事件,而又不用写数据,很容易造成程序空转,CPU消耗100%的现象。
sc.register(selector, SelectionKey.OP_READ);
// 查看SocketChanel.connect(SocketAddress)方法注释可知,finishConnect方法
// 必须被调用,否则连接没有建立完成,注册的事件不会生效
sc.finishConnect();
} else if (key.isReadable()) {//可读事件,socket读缓冲区有数据到达
ByteBuffer buffer=ByteBuffer.allocate(1024);
SocketChannel sc=(SocketChannel) key.channel();
int readBytes=0;
try{
int ret=0;
try{
// 读取目前可读的数据,sc.read(buffer)返回成功复制到buffer中的字节数,
// 此步骤为同步-非阻塞操作,这里体现出了NIO是【同步-非阻塞】IO模式,
// 返回值可能为0,且当已经是流的结尾(Socket已关闭)时返回-1
while((ret=sc.read(buffer))>0){
readBytes+=ret;
}
} finally {
buffer.flip(); // ready for read from buffer.
}
if(readBytes>0){
System.out.println(charset.decode(buffer).toString());
buffer = null;
}
} finally {
if(buffer!=null){
buffer.clear();
}
}
} else if (key.isWritable()) { // 可写事件,socket发送缓冲区有可用空间
// 首先要取消对写事件的注册
key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
SocketChannel sc = (SocketChannel) key.channel();
// 此步骤为同步操作,这里也体现出了NIO是【同步-非阻塞】IO模式,同步直到写入socket发送
// 缓冲区或网络IO出现异常,返回值为成功写入的字节数,当socket发送缓冲区已满时,此处返回0
int writtenSize = sc.write(someByteBuffer);
// 如写入字节数为0,说明socket发送缓冲区已满,此时正是需要注册写事件的时刻
// 其他需要进行写操作的地方,推荐的做法也是直接写入,如果写入返回0,再注册可写事件
if (writtenSize == 0) {
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
}
}
}
// 到此为止,已处理完本次检测出的感兴趣事件,所以需要清理待处理事件集
selector.selectedKeys().clear();
}
}
}
}
Server端
/**
* 《构建高性能的大型分布式Java应用》书中的示例代码, 版权所有:2008---2009
*/
package book.chapter1.tcpnio;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
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;
/**
* 描述:基于Java NIO实现的tcp服务器端
*
* @author bluedavy
* 创建时间: 2008-12-2
*/
public class Server {
public static void main(String[] args) throws Exception {
int port = 9527;
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(port));
System.out.println("Server listen on port: "+port);
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 注册监听新连接到达事件
while (true) {
int nKeys=selector.select(1000);
if (nKeys>0) {
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) { // ServerSocketChannel的ACCEPT事件
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel sc = serverSocketChannel.accept();
if (sc==null) {
continue;
}
sc.configureBlocking(false);
// 这里SocketChannel和ServerSocketChannel共用了同一个Selector对象
sc.register(selector, SelectionKey.OP_READ); // SocketChannel注册可读事件
} else if (key.isReadable()) { // SocketChannel的READ事件
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel sc=(SocketChannel) key.channel();
int readBytes = 0;
String message = null;
try{
int ret;
try{
while ((ret=sc.read(buffer)) > 0) { // 一直读,直到buffer读SocketChannel中没有数据或者buffer中没有可用空间,就返回0
readBytes+=ret;
}
} catch (Exception e) {
readBytes=0;
// IGNORE
} finally {
buffer.flip();
}
if (readBytes > 0) {
message=Charset.forName("UTF-8").decode(buffer).toString(); // Charset.forName("UTF-8") 获取到的Charset是单例的
buffer = null; //释放buffer
}
} finally {
if (buffer != null) {
buffer.clear();
}
}
if (readBytes > 0) {
System.out.println("Message from client: "+ message);
if ("quit".equalsIgnoreCase(message.trim())) {
sc.close();
selector.close();
System.out.println("Server has been shutdown!");
System.exit(0);
}
String outMessage="Server response:"+message;
sc.write(Charset.forName("UTF-8").encode(outMessage));
}
}
}
// 清除已处理过的事件
selector.selectedKeys().clear();
}
}
}
}