public class NioEchoServer {
private static final int BUF_SIZE = 256;
private static final int TIMEOUT = 3000;
public static void main(String args[]) throws Exception {
// 打开服务端 Socket,可以监听新进来的TCP连接的通道,可多个,可选择阻塞和非阻塞模式获取
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 打开 Selector 唤起一个选择器
Selector selector = Selector.open();
// 服务端 Socket 监听8080端口, 并配置为非阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(7777));
serverSocketChannel.configureBlocking(false);
// 将 channel 注册到 selector 中.
// 通常我们都是先注册一个 OP_ACCEPT 事件, 然后在 OP_ACCEPT 到来时, 再将这个 Channel 的 OP_READ
// 注册到 Selector 中.
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//select上的channels已经准备好IO操作
//这是个阻塞方法,入参为超时时间,返回值为准备好channel的个数,可能是0
// 通过调用 select 方法, 阻塞地等待 channel I/O 可操作
if (selector.select(TIMEOUT) == 0) {
System.out.print(".");
continue;
}
// 获取 I/O 操作就绪的 SelectionKey, 通过 SelectionKey 可以知道哪些 Channel 的哪类 I/O 操作已经就绪.
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 当获取一个 SelectionKey 后, 就要将它删除, 表示我们已经对这个 IO 事件进行了处理.
keyIterator.remove();
if (key.isAcceptable()) {
System.out.println("1");
// 当 OP_ACCEPT 事件到来时, 我们就有从 ServerSocketChannel 中获取一个 SocketChannel,
// 代表客户端的连接
// 注意, 在 OP_ACCEPT 事件中, 从 key.channel() 返回的 Channel 是 ServerSocketChannel.
// 而在 OP_WRITE 和 OP_READ 中, 从 key.channel() 返回的是 SocketChannel.
SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
clientChannel.configureBlocking(false);
//在 OP_ACCEPT 到来时, 再将这个 Channel 的 OP_READ 注册到 Selector 中.
// 注意(1)这里我们如果没有设置 OP_READ 的话, 即 interest set 仍然是 OP_CONNECT 的话, 那么 select 方法会一直直接返回.
//(2)这里不能注册OP_WRITE 事件,因为socket只要send buffer不满就可以写!!!
// 刚开始send buffer为空,那么程序会循环执行打印3的操作,而没有真正写的内容,造成CPU负载很高缺没干事
clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(BUF_SIZE));
}
if (key.isReadable()) {
System.out.println("2");
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
long bytesRead = clientChannel.read(buf);
if (bytesRead == -1) {
clientChannel.close();
} else if (bytesRead > 0) {
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
System.out.println("Get data length: " + bytesRead);
}
}
if (key.isValid() && key.isWritable()) {
System.out.println("3");
ByteBuffer buf = (ByteBuffer) key.attachment();
//The limit is set to the current position and then the position is set to zero
//保证读取的内容从头开始,并且读取的长度是已经写入的长度
buf.flip();
SocketChannel clientChannel = (SocketChannel) key.channel();
clientChannel.write(buf);
if (!buf.hasRemaining()) {
//交替注册读写操作,实现echo的功能
key.interestOps(SelectionKey.OP_READ);
}
//清空buffer,清理已读取长度的buffer
buf.compact();
}
}
}
}
}
Socket 和ServerSocke 是一对 他们是java.net下面实现socket通信的类SocketChannel 和ServerSocketChannel是一对 他们是java.nio下面实现通信的类 支持异步通信。
- 服务器必须先建立ServerSocket或者ServerSocketChannel 来等待客户端的连接
- 客户端必须建立相对应的Socket或者SocketChannel来与服务器建立连接
- 服务器接受到客户端的连接受,再生成一个Socket或者SocketChannel与此客户端通信
8