讲解
选择器Selector是NIO中的重要技术之⼀。它与SelectableChannel联合使⽤实现了⾮阻塞的多路复⽤。使⽤它可以节省CPU资源,提⾼程序的运⾏效率。
"多路"是指:服务器端同时监听多个“端⼝”的情况。每个端⼝都要监听多个客户端的连接。
服务器端的多路复用效果

使⽤了多路复⽤,只需要⼀个线程就可以处理多个通道,降低内存占⽤率,减少CPU切换时间,在⾼并发、⾼频段业务环境下有⾮常重要的优势
选择器Selector的获取和注册
概述:Selector被称为:选择器,也被称为:多路复⽤器,可以把多个Channel注册到⼀个Selector选择器上, 那么就可以实现利⽤⼀个线程来处理这多个Channel上发⽣的事件,并且能够根据事件情况决定Channel读写。这样,通过⼀个线程管理多个Channel,就可以处理⼤量⽹络连接了, 减少系统负担,提⾼效率。因为线程之间的切换对操作系统来说代价是很⾼的,并且每个线程也会占⽤⼀定的系统资源。所以,对系统来说使⽤的线程越少越好。
作⽤: ⼀个Selector可以监听多个Channel发⽣的事件, 减少系统负担 , 提⾼程序执⾏效率
Selector选择器的获取
Selector selector = Selector.open()
注册Channel到Selector
通过调⽤ channel.register(Selector sel, int ops)⽅法来实现
注册:
channel.configureBlocking(false);// 设置⾮阻塞SelectionKey key =channel.register(selector,SelectionKey.OP_READ )
register()⽅法的第⼆个参数:是⼀个int值,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件,⽽且可以使⽤SelectionKey的四个常量表示:
- 连接就绪--常量:SelectionKey.OP_CONNECT
-
接收就绪--常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注册时只能使⽤此项)
-
读就绪--常量:SelectionKey.OP_READ
-
写就绪--常量:SelectionKey.OP_WRITE
注意:对于ServerSocketChannel在注册时,只能使⽤OP_ACCEPT,否则抛出异常。
代码:
public class Test11 {
public static void main(String[] args) throws IOException {
//Selector(选择器)
//案例演示,监听一个通道
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc1 = ServerSocketChannel.open();
//绑定端口号
ssc1.bind(new InetSocketAddress(7777));
//设置非阻塞
ssc1.configureBlocking(false);
//获取Selector选择器对象
Selector selector = Selector.open();
//服务器通道的accept()交给选择器来处理
//注册Channel到Selector选择器上
ssc1.register(selector, SelectionKey.OP_ACCEPT);
}
}
示例:服务器创建3个通道,同时监听3个端⼝,并将3个 通道注册到⼀个选择器中
public class Test12 {
public static void main(String[] args) throws IOException {
//服务器创建三个通道,同时监听三个端口,并将三个通道注册到一个选择器中
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc1 = ServerSocketChannel.open();
//绑定端口号
ssc1.bind(new InetSocketAddress(7777));
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc2 = ServerSocketChannel.open();
//绑定端口号
ssc2.bind(new InetSocketAddress(888));
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc3 = ServerSocketChannel.open();
//绑定端口号
ssc3.bind(new InetSocketAddress(9999));
//设置阻塞
ssc1.configureBlocking(false);
ssc2.configureBlocking(false);
ssc3.configureBlocking(false);
//获取Selector选择器对象
Selector selector = Selector.open();
//把服务器通道的accept()交给选择器来处理
//注册Channel到Selector选择器上
ssc1.register(selector, SelectionKey.OP_ACCEPT);
ssc2.register(selector, SelectionKey.OP_ACCEPT);
ssc3.register(selector, SelectionKey.OP_ACCEPT);
}
}
Selector的常用方法
- Selector的select()方法:
- 作用:服务器等待客户端连接的方法
-
阻塞问题:
-
在连接到第⼀个客户端之前,会⼀直阻塞
-
当连接到客户端后,如果客户端没有被处理,该⽅法会计⼊不阻塞状态
-
当连接到客户端后,如果客户端有被处理,该⽅法⼜会进⼊阻塞状态
-
public class Test13 {
public static void main(String[] args) throws IOException {
//Selector的常用方法
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc1 = ServerSocketChannel.open();
//绑定端口号
ssc1.bind(new InetSocketAddress(1234));
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc2 = ServerSocketChannel.open();
//绑定端口号
ssc2.bind(new InetSocketAddress(456));
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc3 = ServerSocketChannel.open();
//绑定端口号
ssc3.bind(new InetSocketAddress(789));
// 设置非阻塞
ssc1.configureBlocking(false);
ssc2.configureBlocking(false);
ssc3.configureBlocking(false);
//获取Selector选择器对象
Selector selector = Selector.open();
//把服务器通道的accept()交给选择器来处理
//注册Channel到Selector选择器上
ssc1.register(selector, SelectionKey.OP_ACCEPT);
ssc2.register(selector, SelectionKey.OP_ACCEPT);
ssc3.register(selector, SelectionKey.OP_ACCEPT);
//死循环一直接受客户端的连接请求
while (true){
System.out.println(1);
//服务器等待客户端的连接
selector.select();//阻塞
System.out.println(2);
//处理客户端请求的代码--》暂时看不懂,先放着
Set<SelectionKey> keySet = selector.selectedKeys();//存储所有的连接的服务器Channel对象
for (SelectionKey key : keySet) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
System.out.println("...开始处理,接受数据,代码省略。。。");
}
}
}
}
Selector的selectedKeys()方法
public class Test14 {
public static void main(String[] args) throws IOException {
//Selector的selectedKeys()方法
//获取已连接的所有的通道集合
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc1 = ServerSocketChannel.open();
//绑定端口号
ssc1.bind(new InetSocketAddress(7777));
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc2 = ServerSocketChannel.open();
//绑定端口号
ssc2.bind(new InetSocketAddress(7777));
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc3 = ServerSocketChannel.open();
//绑定端口号
ssc3.bind(new InetSocketAddress(7777));
//设置非阻塞
ssc1.configureBlocking(false);
ssc2.configureBlocking(false);
ssc3.configureBlocking(false);
//获取Selector选择器对象
Selector selector = Selector.open();
//把服务器通道的accept()交给选择器来处理
//注册Channel到Selector选择器上
ssc1.register(selector, SelectionKey.OP_ACCEPT);
ssc2.register(selector, SelectionKey.OP_ACCEPT);
ssc3.register(selector, SelectionKey.OP_ACCEPT);
// 获取所有被连接的服务器Channel对象的Set集合
// 该Set集合中的元素类型是SelectionKey,该SelectionKey类其实就是对Channel的⼀个封装
Set<SelectionKey> keySet = selector.selectedKeys();
System.out.println("被连接的服务器对象有多少个:"+keySet.size() + "");//0
//死循环一直接受客户端的连接请求
while (true){
System.out.println(1);
//服务器等待客户端的连接
selector.select();//阻塞
System.out.println(2);
System.out.println("被连接的服务器对象个数:"+keySet.size());//有多少个客户端连接服务器成功,就打印几
}
}
}
Selector的keys()⽅法
public class Test15 {
public static void main(String[] args) throws IOException {
//Selector的keys()方法
//获取已注册的所有通道集合
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc1 = ServerSocketChannel.open();
//绑定端口号
ssc1.bind(new InetSocketAddress(7777));
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc2 = ServerSocketChannel.open();
//绑定端口号
ssc2.bind(new InetSocketAddress(8888));
//获取ServerSocketChannel服务器通道对象
ServerSocketChannel ssc3 = ServerSocketChannel.open();
//绑定端口号
ssc3.bind(new InetSocketAddress(9999));
// 设置非阻塞
ssc1.configureBlocking(false);
ssc2.configureBlocking(false);
ssc3.configureBlocking(false);
//获取Selector选择器对象
Selector selector = Selector.open();
//把服务器通道的accept()交给选择器来处理
//这侧Channel到Selector选择器上
ssc1.register(selector, SelectionKey.OP_ACCEPT);
ssc2.register(selector, SelectionKey.OP_ACCEPT);
ssc3.register(selector, SelectionKey.OP_ACCEPT);
//获取所有被连接的服务器Channel对象Set集合
//该Set集合的元素类型是SelectionKey,该SelectionKey类其实就是对Channel的封装
Set<SelectionKey> keySet = selector.selectedKeys();
System.out.println("被连接的服务器对象有多少个:"+keySet.size() + "");//0
//获取所有被注册的服务器Channel对象Set集合
//该Set集合中的元素类型是SelectionKey,该SelectionKey类其实就是对Channel的一个封装
Set<SelectionKey> keys =selector.keys();
System.out.println("被注册的服务器对象有多少个:"+keySet.size());//3
//死循环一直接受客户端的连接请求
while (true){
System.out.println(1);
//服务器等待客户的连接
selector.select();//阻塞
System.out.println(2);
System.out.println("被连接的服务器对象个数:"+keySet.size());//有多少客户端链接服务器成功就打印几
System.out.println("被注册的服务器对象个数:"+keys.size());//选择器上注册了多少个服务器Channel,就打印几
}
}
}
Selector多路复用
public class Test16 {
public static void main(String[] args) throws IOException {
//Selector多路复用
//创建3个服务器Channel对象,并绑定端口号
ServerSocketChannel scc1 = ServerSocketChannel.open();
scc1.bind(new InetSocketAddress(7777));
ServerSocketChannel scc2 = ServerSocketChannel.open();
scc1.bind(new InetSocketAddress(8888));
ServerSocketChannel scc3 = ServerSocketChannel.open();
scc1.bind(new InetSocketAddress(9999));
//把三个服务器Channel对象设置成非阻塞
scc1.configureBlocking(false);
scc2.configureBlocking(false);
scc3.configureBlocking(false);
//3.获得Selector选择器
Selector selector = Selector.open();
//4.把3个服务器Channel对象对象注册到同一个Selector选择器上,指定监听事件
scc1.register(selector, SelectionKey.OP_ACCEPT);
scc2.register(selector, SelectionKey.OP_ACCEPT);
scc3.register(selector, SelectionKey.OP_ACCEPT);
//死循环去等待客户端的连接
while (true){
//服务器等待客户端连接
System.out.println(1);
selector.select();
//获取所有被连接的服务器Channel对象Set集合
Set<SelectionKey> keySet = selector.selectedKeys();//2
// 7.循环遍历所有被连接的服务器Channel对象,获取每⼀个被连接的服务器Channel对象
for (SelectionKey key : keySet){
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
// 9.处理客户端的请求
// 9.1 获取连接的客户端对象
SocketChannel sc = ssc.accept();
//创建ByteBuffer缓冲数组
ByteBuffer b = ByteBuffer.allocate(1024);
//读取数据
int len = sc.read(b);
//打印输出
System.out.println(new String(b.array(),0,len));
//释放资源
sc.close();
}
}
}
}
NIO2-Aio(异步、非阻塞)
AIO概述
同步,异步,阻塞,⾮阻塞概念回顾

AIO相关类和⽅法介绍
AIO是异步IO的缩写,虽然NIO在⽹络操作中,提供了⾮阻塞的⽅法,但是NIO的IO⾏为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程⾃⾏进⾏IO操作,IO操作本身是同步的。
在JDK1.7中,这部分内容被称作NIO.2---->AIO,主要在Java.nio.channels包下增加了下⾯四个异步通道:
- AsynchronousSocketChannel
- AsynchronousServerSocketChannel
- AsynchronousFileChannel
- AsynchronousDatagramChannel
在AIO编程中,发出⼀个事件(accept read write.connect等)之后要指定事件处理类(回调函数),AIO中的事件处理类是CompletionHandler<V,A>,这个接⼝定义了如下两个⽅
法,分别在异步操作成功和失败时被回调。
void completed(V result, A attachment); 成功
void failed(Throwable exc, A attachment);
AIO 异步⾮阻塞连接
分析
- 获取AsynchronousServerSocketChannel对象,绑定端⼝
-
异步接收客户端请求
-
void accept(A attachment,CompletionHandler<AsynchronousSocketChannel,?super A> handler)
-
第⼀个参数: 附件,没啥⽤,传⼊null即可
-
第⼆个参数: CompletionHandler抽象类 ,AIO中的事件处理类
-
void completed(V result, A attachment);异步连接 成功,就会⾃动调⽤这个⽅法
-
void failed(Throwable exc, A attachment);异步连接失败,就会⾃动调⽤这个⽅法
服务器端:
public static void main(String[] args) throws
IOException {
//服务器异步的连接⽅法
//创建对象
AsynchronousServerSocketChannel assc =AsynchronousServerSocketChannel.open();
//绑定端⼝
assc.bind(new InetSocketAddress(8000));
//【异步⾮阻塞】⽅式!!!!!连接客户端
System.out.println("11111111111");
assc.accept(null, newCompletionHandler<AsynchronousSocketChannel,
Object>() {
@Override
//回调函数,当成功连接了客户端之后,会⾃动回来调⽤这个⽅法
public voidcompleted(AsynchronousSocketChannel result,Object attachment) {
//completed是完成,如果连接成功会⾃动调⽤这个⽅法
System.out.println("completed");
}
@Override
public void failed(Throwable exc,Object attachment) {
//failed是失败,如果连接失败会⾃动调⽤这个⽅法
}
});
System.out.println("22222222222");
//写⼀个死循环让程序别结束(因为程序结束了⽆法演示效果)
while(true){
}
}
-AIO 异步⾮阻塞连接和异步
分析
- 获取AsynchronousServerSocketChannel对象,绑定端⼝
-
异步接收客户端请求
-
在CompletionHandler的completed⽅法中异步读数据
//异步⾮阻塞连接和读取
public static void main(String[] args)throws IOException {
//创建对象
AsynchronousServerSocketChannel assc =AsynchronousServerSocketChannel.open();
//绑定端⼝
assc.bind(new InetSocketAddress(8000));
//异步⾮阻塞连接!!!!
//第⼀个参数是⼀个附件
System.out.println(1);
assc.accept(null, newCompletionHandler<AsynchronousSocketChannel,Object>() {
@Override
public void completed(AsynchronousSocketChannel s, Objectattachment) {
//如果连接客户端成功,应该获取客户端发来的数据
//completed()的第⼀个参数表示的是Socket对象.
System.out.println(5);
//创建数组
ByteBuffer buffer =ByteBuffer.allocate(1024);
//异步⾮阻塞读!!!!!
System.out.println(3);
s.read(buffer, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer len, Object attachment) {
//读取成功会⾃动调⽤这个⽅法
//completed()⽅法的第⼀个参数是read()读取到的实际个数
//打印数据
System.out.println(6);
System.out.println(new String(buffer.array(),0,len));
}
@Override
public voidfailed(Throwable exc, Object attachment) {
}
});
System.out.println(4);
}
@Override
public void failed(Throwable exc,Object attachment) {
}
});
System.out.println(2);
//让程序别结束写⼀个死循环
while(true){
}
}
JavaNIOSelector详解:多路复用与非阻塞IO
Selector是JavaNIO中的关键组件,它允许多个Channel在一个线程中进行非阻塞的多路复用,提高程序运行效率。Selector可以通过一个线程处理多个Channel上的事件,如连接、接受、读写等。使用Selector可以减少线程切换的开销,尤其适合高并发、高频度的网络服务。文章还介绍了Selector的获取、注册、常用方法以及NIO2中的AIO(异步非阻塞IO)概念。

被折叠的 条评论
为什么被折叠?



