NIO多路复用机制

NIO多路复用机制

客户端与服务器每次网络通讯, 会建立一个连接, 创建socket, 以soket为媒介, 发送和接收输入输出流.进行流的读写操作.

介绍下BIO、NIO和AIO

BIO(blocking I/O) 同步阻塞
JDK1.4之前,是面向流传输的, 传输效率慢, 客户端每次发起一个请求, 建立一个连接, 都创建一个socket对象, 不管有没有流的传输, 服务器都创建一个线程, 但有些线程不会执行流的读写操作是无效的. 线程多了会占用过多的资源,浪费堆的内存空间(可以采用线程池去管理线程, 但是如果线程池满的情况下, 再创建新线程会等待空余线程去复用)不适合高并发情况
io的阻塞是针对流是否可以同时读写来说的,stream只是支持单向的读写

NIO (non-blocking I/O)同步非阻塞
组成部分: 通道, 缓冲区, 选择器
JDK1.4开始支持, 引入的面向缓冲区的, 缓冲区buffer支持同时读写操作, 服务器不需要对每个请求都创建线程,
执行流程: 客户端发起请求, 请求通过chanel通道注册到selector连接器上, selector进行while(true)轮询, 当轮询到到有读写操作时,才创建线程. 效率提升.适用连接数量较多,但连接较短的操作, 比如聊天室。
在这里插入图片描述

AIO (Asynchronous I/O) 异步非阻塞

AIO的基本流程是:用户线程通过系统调用,告知kernel内核启动某个IO操作,用户线程返回。kernel内核在整个IO操作(包括数据准备、数据复制)完成后,通知用户程序,用户执行后续的业务操作。

在这里插入图片描述

BIO/NIO/AIO使用场景
并发连接数不多时采用BIO,因为它编程和调试都非常简单,但如果涉及到高并发的情况,应选择NIO或AIO,更好的建议是采用成熟的网络通信框架Netty。

NIO中select、poll 、 epoll模式
文件描述符fd:
在linux中,一切皆文件,fd是非负整数,用来标明每一个被进程所打开的文件
select模式
当客户端发起一个socket请求, 在linux系统下看做一个文件描述符fd, select模式的fd存放bitmap里, 支持fd的大数量是1024。
 

在这里插入图片描述

 

在这里插入图片描述

poll模式
1.相比select, 把fd的bitMap变为了fd的链表,
2.fd的个数上限大于1024个, 但依旧会有用户态和内核态切换的性能问题.
3.依旧存在时间复杂度O(n)的问题, 提升性能不大

epoll模式(基于事件驱动)
在这里插入图片描述

执行过程
1.epoll_create()
创建好请求socket的fd; 准备空间, 在高速cache中生成Map(就那个红黑树) 和 1个List(就那个事件链表)
2. epoll_ctl(epollfd, my_events)
只把有效fd事件(比如:读写等)注册进红黑树, 以便支持快速插入、删除、修改等操作而不用再变遍历 ,然后通过回调函数, 写进事件链表里.
3. epoll_wait(epollfd, ready_events)
检查事件链表中有数据就处理, 没有数据就睡眠.

epoll优点:
1.相比select和poll, epoll获取有效fd的时间复杂度是O(1), epoll每次只扫描事件链表的数据, 直接取符合条件的fd, 所以复杂度O(1)
2.epoll提供高速cache, 共享用户态和内核态, 不需要内核状态之前的切换.

Netty简述

Netty基于NIO的Reactor的多线程模型,
有2个线程, 一个是boss线程,一个是worker线程,
boss线程执行accept方法, 集中接收客户端的请求,建立连接
worker线程负责处理各自IO事件.

 

 

 

 

 

 

 

 

 

 

 

 

### nIO多路复用的定义 NIO多路复用是一种高效的I/O处理方式,允许单个线程管理多个网络连接。通过这种方式,程序可以在一个线程中监听多个通道的状态变化(如可读、可写),从而避免了传统同步阻塞I/O模式下需要为每个连接创建独立线程所带来的开销[^1]。 --- ### NIO多路复用的工作原理 NIO多路复用的核心依赖于`Selector`类。以下是其主要工作机制: 1. **注册Channel到Selector** 将多个`SelectableChannel`对象(如`ServerSocketChannel`或`SocketChannel`)注册到同一个`Selector`实例上,并指定感兴趣的事件类型(如`SelectionKey.OP_READ`表示关注读操作)。这一步使得`Selector`能够感知这些通道上的状态变化[^4]。 2. **轮询准备好的Channel** 调用`select()`方法让当前线程进入等待状态,直到某些已注册的通道发生了所关心的操作为止。此时返回的结果集包含了所有已经准备好对应操作的通道列表[^3]。 3. **遍历并处理事件** 对于每一个处于就绪状态下的通道,逐一取出它们关联的数据流或者发起新的通信动作;完成之后再继续回到第二步循环监测新到来的变化情况[^5]。 这种设计极大地减少了因频繁创建销毁大量短生命周期轻量级单元而带来的性能损耗问题,在应对大规模并发访问请求时表现出显著优越性。 --- ### 应用场景分析 #### 高并发环境 由于能够在单一进程中同时维护成千上万个活跃链接的能力,因此特别适合应用于Web服务器、即时通讯工具以及其他任何可能面临极高频率交互需求的服务端架构之中[^1]。 例如在一个典型的聊天室应用里,如果采用传统的BIO模型,则每新增一位参与者都需要额外分配一个新的服务进程/线程来单独为其提供支持——随着人数增加不仅消耗内存空间还会加剧调度压力。然而借助NIO多路复用方案则可以通过少量固定数目的工作者线程池即可满足相同规模甚至更大范围内的客户需求。 另外值得注意的是尽管如此高效但也并非毫无代价:持续不断地扫描各个文件描述符是否具备活动迹象本身就会耗费一定计算资源;而且当实际存在的有效连接数目过多时还可能导致上下文切换成本上升等问题出现[^3]。 为了缓解这些问题,在具体工程实践中往往会结合诸如Reactor模式这样的高级编程范型进一步优化整体表现效果。比如Netty框架内部正是采用了类似的双层结构设计理念:其中Boss组专门负责接受外部来访者的接入请求并将成功建立起来的新会话分发给Worker组成员分别承担后续具体的事务逻辑处理职责[^4]。 --- ### 示例代码展示 下面给出一段简单的基于NIO Selector实现的TCP回显服务器示例: ```java 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.Iterator; public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public void start() throws IOException { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(port)); serverSocketChannel.configureBlocking(false); Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); if (!key.isValid()) continue; if (key.isAcceptable()) handleAccept(key); if (key.isReadable()) handleRead(key); } } } private void handleAccept(SelectionKey key) throws IOException { ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); SocketChannel clientChannel = serverSocketChannel.accept(); System.out.println("Accepted connection from " + clientChannel.getRemoteAddress()); clientChannel.configureBlocking(false); clientChannel.register(key.selector(), SelectionKey.OP_READ); } private void handleRead(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = channel.read(buffer); if (bytesRead == -1) { channel.close(); return; } String message = new String(buffer.array()).trim(); System.out.println("Received: " + message); channel.write(ByteBuffer.wrap(("Echo: " + message).getBytes())); } public static void main(String[] args) throws IOException { new EchoServer(8080).start(); } } ``` 此段代码展示了如何利用NIO中的`Selector`机制构建一个多路复用的简单服务器应用程序。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值