package com.test.server;
import java.io.IOException;
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.util.Iterator;
import java.util.Set;
/**
* @ClassName: PlainNioEchoServer
* @Description: 以NIO方式实现EchoServer
* @author: yl
* @date: 2014年7月10日 下午4:50:46
*/
public class PlainNioEchoServer {
public void serve(int port) throws IOException {
System.out.println("Listening for connections on port " + port);
//打开服务器套接字通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
//获取服务器的套接字
ServerSocket ss = serverChannel.socket();
//创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值
InetSocketAddress address = new InetSocketAddress(port);
//绑定套接字地址
ss.bind(address);
//调整通道的阻塞模式
serverChannel.configureBlocking(false);
//创建选择器(通道对象的多路复用器)
Selector selector = Selector.open();
//向选择器注册通道(用户坚挺通道的访问操作)
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
try {
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
// handle in a proper way
break;
}
//已选择键集 是这样一种键的集合,即在前一次选择操作期间,检测每个键的通道是否已经至少为该键的相关操作集所标识的一个操作准备就绪
Set readyKeys = selector.selectedKeys();
//遍历已选择键集
Iterator iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = (SelectionKey) iterator.next();
iterator.remove();
try {
//键的通道是否已准备好接受新的套接字连接
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
System.out.println("Accepted connection from " + client);
client.configureBlocking(false);
//向选择器注册client通道
client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ,
ByteBuffer.allocate(100));
}
//键的通道是否已准备好进行读取
if (key.isReadable()) {
//完成客户端通道数据的读取
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
client.read(output);
}
//键的通道是否已准备好进行写入
if (key.isWritable()) {
//完成客户端通道数据的写入
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
output.flip();
client.write(output);
//压缩缓冲区
output.compact();
}
} catch (IOException ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
}
}
}
}
}
}
主要步骤是:
1.创建服务器连接通道,获取套接字;
2.绑定套接字网络地址,设置服务通道阻塞模式;
3.创建选择器,将服务通道注册到选择器上;
4.不断循环选择器的select方法,阻塞至直到有IO操作或者线程中断或者调用选择器的wakeup方法才返回,接着获取已准备就绪IO操作的选择器key的集合;
5.将获取的已准备就绪IO操作的选择器key的集合进行循环遍历,判断key对应的IO操作,主要有连接、读取、写入3种操作, 然后分别对不同的操作生成客户通道进行相应的处理;
阻塞IO的工作模式:
非阻塞IO(NIO)的工作模式: