通过Netty权威指南上的一个简单NIO时间服务器
TimeServer
import org.apache.commons.lang.StringUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
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.Date;
import java.util.Iterator;
import java.util.Set;
/**
* Created by liqiushi on 2017/12/8.
*/
public class TimeServer implements Runnable {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private volatile boolean stop;
public TimeServer(int port) {
try {
//1、打开ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
//2、绑定监听端口
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("The time server start! port:" + port);
} catch (IOException e) {
e.printStackTrace();
}
//3、启用多路复用
}
@Override
public void run() {
while (!stop) {
try {
selector.select(1000);
Set selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
SelectionKey selectionKey = null;
while (iterator.hasNext()) {
selectionKey = iterator.next();
iterator.remove();
try {
handleInPut(selectionKey);
} catch (Exception e) {
if (selectionKey != null) {
selectionKey.cancel();
if (selectionKey.channel() != null) {
selectionKey.channel().close();
}
}
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void stop() {
this.stop = true;
}
private void handleInPut(SelectionKey key) throws IOException {
if (key.isValid()) {
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = socketChannel.read(readBuffer);
if (readBytes > 0) {
//转换成写模式
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("The time server recieve order : " + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ?
new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
//write
dowrite(socketChannel, currentTime);
} else if (readBytes < 0) {
key.cancel();
socketChannel.close();
} else {
//0字节
}
}
}
}
private void dowrite(SocketChannel socketChannel, String response) throws IOException {
if (!StringUtils.isBlank(response)) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
socketChannel.write(writeBuffer);
}
}
public static void main(String[] args) {
TimeServer timeServer = new TimeServer(8001);
new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
}
}
几个疑问
selector
有几个keySet:1、注册(publicKeys) 2、准备就绪(selectedKeys)3、调用了cancel()后,将channel对应的key加入cancelledKeys集中
cancel()
调用了SelectionKey对象的cancel方法,这个SelectionKey对象就会被加入到cancelled-keys集合中,表示这个SelectionKey对象已经被取消。
在每次选择操作期间,都可以将键添加到选择器的已选择键集以及从中将其移除,并且可以从其键集和已取消键集中将其移除。选择是由 select()、select(long) 和 selectNow() 方法执行的,执行涉及三个步骤:
1.将已取消键集中的每个键从所有键集中移除(如果该键是键集的成员),并注销其通道。此步骤使已取消键集成为空集。
2.在开始进行选择操作时,应查询基础操作系统来更新每个剩余通道的准备就绪信息,以执行由其键的相关集合所标识的任意操作。对于已为至少一个这样的操作准备就绪的通道,执行以下两种操作之一:
- a.如果该通道的键尚未在已选择键集中,则将其添加到该集合中,并修改其准备就绪操作集,以准确地标识那些通道现在已报告为之准备就绪的操作。丢弃准备就绪操作集中以前记录的所有准备就绪信息。
- b. 如果该通道的键已经在已选择键集中,则修改其准备就绪操作集,以准确地标识所有通道已报告为之准备就绪的新操作。保留准备就绪操作集以前记录的所有准备就绪信息;换句话说,基础系统所返回的准备就绪操作集是和该键的当前准备就绪操作集按位分开 (bitwise-disjoined) 的。
3.如果在此步骤开始时键集中的所有键都有空的相关集合,则不会更新已选择键集和任意键的准备就绪操作集。
如果在步骤2的执行过程中要将任意键添加到已取消键集中,则处理过程如步骤1。
JAVA NIO 不是同步非阻塞I/O吗,为什么说JAVA NIO提供了基于Selector的异步网络I/O?
I/O操作 在前面的文章提到,分为两步:
- 询问内核空间是否有接受到数据的到来
- 进行read/write读写操作
- JAVA NIO是基于select 多路复用模型,linux下(epoll),那么结论就是同步非阻塞,阻塞与非阻塞在于是否需要用户阻塞于询问数据的过程,而同步异步在于I/O的具体操作(内核空间到 用户缓冲区的copy),数据到来,NIO依旧是一个同步操作的过程,异步则是I/O由内核完成,当完成时再来提醒用户进行其他的操作。
下面回答第二个问题:为什么说是异步网络I/O(引用他人之言)
而说java nio提供了异步处理,这个异步应该是指编程模型上的异步。基于reactor模式的事件驱动,事件处理器的注册和处理器的执行是异步的。