NIO和Netty全文
Selector
- Netty的IO线程NioEventLoop聚合了Selector选择器
(多路复用器) - 选择器,可以实现单线程管理多个通道/网络连接,用于检查多个通道是否有事件发生,
- 用更少的线程管理通道,避免线程上下文切换带来的开销,将非阻塞IO的空闲时间用于在其他通道上执行IO操作
- SelectableChannel,可选择通道,只用继承了
SelectableChannel才可以被复用
并不是所有通道都可以被selector复用,
FileChannel不可以
- 一个通道可以被注册到多个选择器上,但是一个通道对于一个选择器只能被注册一次
- selector关注的是
通道做好注册行为准备之后,进行行为之前的状态,采用主动轮询的方式,监听各个通道所处的状态,监听到感兴趣的就绪状态后,将该通道放入选择键集合中 - Channel必须在非阻塞模式下
- 四个模式的触发情况
- accept,接收到连接,适用ServerSocketChannel
- read,该通道被写入数据或信号
- write,该通道可写
- connect,进行连接操作,可以获取连接的信息
方法示例
//创建
Selector selector = Selector.open();
//创建通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//非阻塞模式
ssc.configureBlocking(false);
//绑定连接
ssc.bind(new InetSocketAddress(8888));
//注册通道,关注的是接收的行为
//获取支持的操作,16进制的表示
int i = ssc.validOps();
//16,只有接受操作
System.out.println(i);
ssc.register(selector, SelectionKey.OP_ACCEPT);
//查询已经就绪的通道操作
Set<SelectionKey> sk = selector.selectedKeys();
Iterator<SelectionKey> it = sk.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
//判断就绪状态
if (key.isAcceptable()) {
} else if (key.isConnectable()) {
} else if (key.isValid()) {
} else if (key.isReadable()) {
} else if (key.isWritable()) {
}
}
it.remove();
select
- select(),阻塞到至少有一个通道就绪
sel.select()
- selectNow() 只要有通道就绪立刻返回,没有就直接跳过
sel.selectNow()
- select(long timeout),最长阻塞时间为timeout毫秒
sel.select(10)
客户端
//获取通道,绑定服务器的主机和端口号
SocketChannel sc = SocketChannel.open(new InetSocketAddress("localhost", 9999));
//切换到非阻塞模式
sc.configureBlocking(false);
System.out.println(sc.validOps());
//创建buffer并写入
ByteBuffer bb = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String str = scanner.next();
bb.put((new Date().toString() + " - " + str).getBytes());
//模式切换,还原指针
bb.flip();
sc.write(bb);
bb.clear();
}
//如果客户端使用close关闭,服务端会持续收到这个关闭信号,导致一直存在sk
//当关闭时,读取sc为-1
sc.close();
服务端
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ByteBuffer bb = ByteBuffer.allocate(1024);
//绑定端口号
ssc.bind(new InetSocketAddress(9999));
//注册通道
Selector sel = Selector.open();
ssc.register(sel, SelectionKey.OP_ACCEPT);
//如果有就绪状态,就是>=1
while (sel.select() > 0) {
Set<SelectionKey> sk = sel.selectedKeys();
Iterator<SelectionKey> it = sk.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
//是否有效
if (key.isValid() && key.isAcceptable()) {
//获取连接
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
//将这个链接注册到sel上,进行轮询监听
sc.register(sel, SelectionKey.OP_READ);
} else if (key.isValid() && key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
int len = 0;
//从SocketChannel读取到会是0
while ((len = sc.read(bb)) > 0) {
bb.flip();
System.out.println("receive - " + new String(bb.array(), 0, len));
bb.clear();
}
}
//做完当前key的处理,并清除
it.remove();
}
}
其他api
- 注册时绑定buffer
//为这个通道绑定一个buffer
sc.register(sel, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
- 获取绑定的buffer
ByteBuffer bb = (ByteBuffer) sk.attachment();
- 改变监听事件
sk.interestOps(SelectionKey.OP_WRITE);
Selector细节
- mac中Selector的实际类型为
KQueueSelectorImpl - 所有注册的通道
sel.keys()
NIO编程步骤
- 创建
ServerCocketChannel通道,绑定监听端口 - 设置通道为非阻塞模式
- 创建
Selector选择器 - 把
Channel注册到选择器上,监听连接事件 - 调用
Selector的select方法,监测通道的就绪状态 - 调用
selectedKeys获取到选择键集合 - 遍历就绪channel集合,获取其事件类型,实现具体业务
- 删除处理完的事件