3.NIO选择器(基于NIO的服务器与客户端通讯)
Posted PacosonSWJTU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了3.NIO选择器(基于NIO的服务器与客户端通讯)相关的知识,希望对你有一定的参考价值。
【README】
本文总结自B站《尚硅谷netty》,很不错;
【1】选择器Selector(多路复用器)
【1.1】基本介绍
1)Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个客户端连接,就会使用到Selector(选择器);
2)Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求;
3)只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程;
4)避免了多线程之间的上下文切换导致的开销;
5)特点再说明:
- 5.1) Netty 的 IO 线程NioEventLoop 聚合了 Selector(选择器,也叫多路复用器*),可以同时并发处理成百上千个客户端连接;
- 5.2)当线程从某客户端Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务(即线程不会在没有数据可用的客户端连接上阻塞,而是处理其他客户端请求);
- 5.3) 线程通常将非阻塞IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道;
- 5.4)由于读写操作都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频繁I/O阻塞导致的线程挂起;
- 5.5)一个 I/O 线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升;
【1.2】Selector类相关方法
1)Selector 类是一个抽象类, 常用方法和说明如下:
public abstract class Selector implements Closeable
public static Selector open();//得到一个选择器对象
public int select(long timeout);// 返回IO操作准备就绪的通道的key个数;
// 监控所有注册的通道,当其中有 IO 操作可以进行时,将对应的 SelectionKey 加入到内部集合中并返回,参数用来设置超时时间
public Set<SelectionKey> selectedKeys();//从内部集合中得到所有的 SelectionKey
【补充】
- 补充1) SelectionKey 与 Channel 是映射关系;通过SelectionKey 可以获取到 Channel ;
- 补充2) SelectionKey 是 Selector的一个重要对象;
【注意事项】
1) NIO中的 ServerSocketChannel功能类似ServerSocket,SocketChannel功能类似Socket ;
2) selector 相关方法说明:
selector.select()//阻塞
selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回;
selector.wakeup();// 唤醒selector
selector.selectNow();// 不阻塞,立马返还
【1.3】 SelectionKey在NIO 体系
1)NIO 非阻塞网络编程原理分析图
NIO 非阻塞网络编程相关的(Selector、SelectionKey、ServerScoketChannel和SocketChannel) 关系梳理图;
【图解】 服务器采用NIO非阻塞处理客户端请求步骤:
- Step1)当客户端连接时,会通过ServerSocketChannel 生成 SocketChannel;
- Step2) 把 SocketChannel注册到Selector上,register(Selector sel, int ops),1个selector上可以注册多个SocketChannel ;
- Step3)注册后返回一个SelectionKey,会和该Selector 关联(集合);
- Step4) Selector 监听通道Channel的select方法,返回有事件发生的通道个数.;
- Step5)进一步得到各个SelectionKey (有事件发生);
- Step6)再通过SelectionKey反向获取SocketChannel , 通过方法channel() 得到;
- Step7)可以通过得到的channel , 完成业务处理;
【2】基于NIO实现服务器端和客户端之间的数据简单通讯
1)服务器代码:
/**
* @Description 编写一个 NIO 入门案例,
* 实现服务器端和客户端之间的数据简单通讯(非阻塞)
* @author xiao tang
* @version 1.0.0
* @createTime 2022年08月17日
*/
public class Nioserver024
public static void main(String[] args) throws IOException
// 创建 ServerSocketChannel -> ServerSocket
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);
// 循环等待客户端连接
while (true)
if (selector.select(3000) == 0) // 没有事件发生
System.out.println("服务器等待1秒,无客户端连接");
continue;
// 如果返回值大于0, 表示监听到有事件发生,
// 1.获取相关的 SelectionKey 集合
// 2. selector.selectedKeys 获取关注事件的集合
// 3. 通过 selectionKeys 反向获取通道;
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 4. 遍历 selectionKeys ,使用迭代器遍历
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext())
// 获取到 SelectionKey
SelectionKey key = it.next();
// 根据key 对应的通道,发生的事件做不同处理
if (key.isAcceptable()) // 如果是 OP_ACCEPT,表示有新的客户端连接服务器,则需要产生新的 channel
// 给客户端生成一个 SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
// 需要把 SocketChannel 设置为 非阻塞
socketChannel.configureBlocking(false);
System.out.println("客户端连接成功,生成一个 socketChannel, hashcode=" + socketChannel.hashCode());
// 把 SocketChannel 注册到 Selector 上,监听 OP_READ 事件
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
if (key.isReadable()) // 只读
// 通过key反向获取 Channel
SocketChannel channel = (SocketChannel) key.channel();
// 获取到该 Channel 关联的 buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
// 把channel里的读入到 ByteBuffer
channel.read(buffer);
System.out.println("客户端:" + new String(buffer.array(), StandardCharsets.UTF_8));
// 手动从集合中移除 selectionKey,防止重复操作
it.remove();
2)客户端:
/**
* @Description NIO 客户端
* @author xiao tang
* @version 1.0.0
* @createTime 2022年08月17日
*/
public class NIOClient025
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 text = "hello 成都";
ByteBuffer byteBuffer = ByteBuffer.wrap(text.getBytes(StandardCharsets.UTF_8));
// 发送数据,把buffer中的数据写入到 channel
socketChannel.write(byteBuffer);
System.out.println("任意键结束");
System.in.read();
【演示效果】
以上是关于3.NIO选择器(基于NIO的服务器与客户端通讯)的主要内容,如果未能解决你的问题,请参考以下文章