目录
Selector(选择器)
Selector示意图和特点说明
特点说明:
- Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),可以同时并发处理成百上千个客户端连接。
- 当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。
- 线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。
- 由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。
- 一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
Selector类相关方法
- Selector 类是一个抽象类, 常用方法和说明如下:
public abstract class Selector implements Closeable {
public static Selector open(); //得到一个选择器对象
public int select(long timeout);//监控所有注册的通道,当其中有 IO 操作可以进行时,将
对应的 SelectionKey 加入到内部集合中并返回,参数用来设置超时时间
public Set<SelectionKey> selectedKeys();//从内部集合中得到所有的 SelectionKey
}
注意事项
- NIO中的 ServerSocketChannel功能类似ServerSocket,SocketChannel功能类似Socket
selector 相关方法说明
selector.select(); //阻塞
selector.select(1000); //阻塞1000毫秒,在1000毫秒后返回
selector.wakeup(); //唤醒selector
selector.selectNow(); //不阻塞,立马返还
NIO非阻塞 网络编程原理分析图
- NIO 非阻塞 网络编程相关的(Selector、SelectionKey、ServerScoketChannel和SocketChannel) 关系梳理图
![image.png](https://img-blog.csdnimg.cn/img_convert/1d838747eb4a1399b4c381f7defb9924.png)
- 说明:
- 当客户端连接时,会通过ServerSocketChannel 得到 SocketChannel
- Selector 进行监听 select 方法, 返回有事件发生的通道的个数.
- 将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel
- 注册后返回一个 SelectionKey, 会和该Selector 关联(集合)
- 进一步得到各个 SelectionKey (有事件发生)
- 在通过 SelectionKey 反向获取 SocketChannel , 方法 channel()
- 可以通过得到的 channel , 完成业务处理
NIO非阻塞 网络编程快速入门
案例要求:
- 编写一个 NIO 入门案例,实现服务器端和客户端之间的数据简单通讯(非阻塞)
- 目的:理解NIO非阻塞网络编程机制
NIOServer.java
public class NIOServer {
public static void main(String[] args) throws IOException {
// 创建 ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 得到一个 Selector 对象
Selector selector = Selector.open();
// 绑定一个端口6666,在一个服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
serverSocketChannel.configureBlocking(false);
// 把ServerSocketChannel 注册到 selector ,关心事件为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("注册到Selector中的事件数量:"+selector.keys().size()); // 1
// 循环等待客户端连接
while (true) {
// 这里为我们等待1秒,如果没有事件发生,则返回
if (selector.select(1000) == 0) {
// 没有事件发生
// System.out.println("服务器等待了1秒,无连接");
continue;
}
// 如果返回的大于0,就获取相关的 selector
// 1. 如果返回的大于0,证明已经获取到关注的事件
// 2. selector.selectorKeys() 返回关注事件的集合
// 通过 selectionKeys 反向获取通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
System.out.println("关注的事件数量:"+selector.selectedKeys().size()); // 1
while (keyIterator.hasNext()){
// 获取到SelectorKey
SelectionKey key = keyIterator.next();
// 根据Key对应的通道发生的事件做相应的处理
if(key.isAcceptable()){ // 如果是 OP_ACCEPT,有新的客户端连接
// 该客户端生成一个SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
// 将socketChannel 设置成非阻塞
socketChannel.configureBlocking(false);
// 将socketChannel 注册到selector,关注事件为OP_READ,同时给 socketChannel 关联一个Buffer
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("注册到Selector中的事件数量:"+selector.keys().size()); // 2,3,4
}
if(key.isReadable()){
// 发生 OP_READ 事件
// 通过 Key 反向获取到对应Channel
SocketChannel channel = (SocketChannel) key.channel();
// 获取到 该Channel 关联的Buffer
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
channel.read(byteBuffer);
System.out.println("From 客户端:"+new String(byteBuffer.array()));
}
// 手动从集合中移除当前selectionKey,防止重复操作
keyIterator.remove();
}
}
}
}
NIOClient.java
public class NIOClient {
public static void main(String[] args) throws IOException {
// 得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
// 设置非阻塞
socketChannel.configureBlocking(false);
// 提供服务器IP和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",6666);
// 连接服务器
if(!socketChannel.connect(inetSocketAddress)){
while(!socketChannel.finishConnect()){
System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作。。。");
}
}
// 如果连接成功,就发送数据
String str = "Hello World";
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes(StandardCharsets.UTF_8));
// 发送数据,将buffer数据写入channel
socketChannel.write(buffer);
System.in.read();
}
}
SelectionKey
SelectionKey,表示 Selector 和网络通道的注册关系, 共四种:
- int OP_ACCEPT:有新的网络连接可以 accept,值为 16
- int OP_CONNECT:代表连接已经建立,值为 8
- int OP_READ:代表读操作,值为 1
- int OP_WRITE:代表写操作,值为 4
源码中:
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
SelectionKey相关方法
public abstract class SelectionKey {
public abstract Selector selector();//得到与之关联的 Selector 对象
public abstract SelectableChannel channel();//得到与之关联的通道
public final Object attachment();//得到与之关联的共享数据
public abstract SelectionKey interestOps(int ops);//设置或改变监听事件
public final boolean isAcceptable();//是否可以 accept
public final boolean isReadable();//是否可以读
public final boolean isWritable();//是否可以写
}
ServerSocketChannel
- ServerSocketChannel 在服务器端监听新的客户端 Socket 连接
- 相关方法如下:
public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel{
public static ServerSocketChannel open(),得到一个 ServerSocketChannel 通道
public final ServerSocketChannel bind(SocketAddress local),设置服务器端端口号
public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
public SocketChannel accept(),接受一个连接,返回代表这个连接的通道对象
public final SelectionKey register(Selector sel, int ops),注册一个选择器并设置监听事件
}
SocketChannel
- SocketChannel,网络 IO 通道,具体负责进行读写操作。NIO 把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。
- 相关方法如下:
public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel{
public static ServerSocketChannel open(),得到一个 ServerSocketChannel 通道
public final ServerSocketChannel bind(SocketAddress local),设置服务器端端口号
public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
public SocketChannel accept(),接受一个连接,返回代表这个连接的通道对象
public final SelectionKey register(Selector sel, int ops),注册一个选择器并设置监听事件
}
文章内容部分来源于网络,仅用于学习总结,技术分享,如有侵犯,请联系删除