IO多路复用

文件描述符最多1024个,0stdin,1stdout,2stderro

 IO多路复用
        IO多路转接

      1.  多个IO复用一个进程,不创建新进程和线程,效率高
      2.  不适合处理比较耗时的任务
      
      select
      poll
      epoll

      1. 创建文件描述符集合
      2. 添加文件描述符到集合中
      3. 通知内核开始监测
      4. 根据返回的结果做对应的操作(对io读、写操作)

应用场景:
    1. 构建并发服务器,使用IO多路复用监测多个客户端套接字
    2. 使用io多路复用监测多个IO所对应的通信(如:网络、串口、can....)
    3. 在阻塞io中,进行超时监测

select:
      1. 创建文件描述符集合   fd_set
      2. 添加文件描述符到集合中 void FD_SET(int fd, fd_set *set);
      3. 通知内核开始监测        select
      4. 根据返回的结果做对应的操作(对io读、写操作)
     

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
       功能:监测多路IO
       参数:
                 nfds : 关注的文件描述符中的最大值+1   
                  readfds:关注的读事件的文件描述符集合
                 writefds:关注的写事件的文件描述符结合
                 exceptfds:其他  异常
                 timeout : 超时时间,如果不设置:NULL
      返回值:
              成功:返回到达事件的个数
               失败:-1
              设置了超时时间:超时时间到达但没有事件,返回0
                     

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

epoll:

      1. 创建文件描述符集合                  int epoll_create(int size);
      2. 添加文件描述符到集合中           epoll_ctl()
      3. 通知内核开始监测                     epoll_wait()
      4. 根据返回的结果做对应的操作(对io读、写操作)


int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:
参数:
          epfd:文件描述符集合句柄
          op: 
                   EPOLL_CTL_ADD: 向集合中添加文件描述符
                   EPOLL_CTL_MOD: 修改集合
                   EPOLL_CTL_DEL :删除文件描述符
          
          fd :操作的文件描述符
         event :文件描述符所对应的事件


          typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };

          event : EPOLLIN :  读操作
                       EPOLLOUT : 写事件


    int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
   功能:监测IO事件
   参数:
             epfd : 文件描述符集合句柄
             events : 保存到达事件的结合的首地址
             maxevents : 监测时事件的个数
            timeout:超时时间
                           -1  :不设置超时时间

返回值:
          成功:返回到达事件的个数
          失败:-1
          设置超时:超时时间到达返回0

### 原理 在传统的阻塞IO模型中,每个IO操作都会导致线程阻塞,直到数据准备完毕或发送完毕,且每个线程只能处理一个IO操作,当面临大量并发连接时,需创建大量线程,消耗大量系统资源。而IO多路复用通过使用操作系统提供的IO复用机制,如select、poll或epoll,可同时监听多个文件描述符上的IO事件,实现高效的IO处理。例如select函数和poll函数模型,进程告诉多路复用器(内核)所有的socket号,多路复用器去获取每个socket的状态,当程序获取到某个socket号有事件发生时,就去该socket号上处理对应的事件,如read事件或者recived事件。不过select函数底层是数组,有最大连接数的限制,poll函数底层是链表,无最大连接数的限制。这两种模型都存在需要遍历所有socket(O(N)复杂度)以及重复传递数据(内核无状态,每次都要从用户态向内核态传递所有的socket号去遍历获取状态)的缺点 [^2][^3]。 ### 应用 IO多路复用广泛应用于需要同时处理大量网络连接的场景中,如高并发服务器、即时通讯系统等。在高并发服务器中,可能会同时有大量客户端连接,使用IO多路复用可以高效地处理这些连接上的IO事件;在即时通讯系统里,也需要同时处理多个用户的消息收发,IO多路复用能提升系统的处理效率 [^1]。 ### 实现方式 常见的实现方式有select、poll和epoll。 - **select**:进程将所有需要监听的socket号告知内核,内核会检查这些socket的状态,当有事件发生时通知进程。但由于其底层使用数组存储socket号,有最大连接数的限制,且每次调用都要将所有socket号从用户态复制到内核态,遍历所有socket,效率较低 [^3]。 - **poll**:与select类似,也是让内核检查多个socket的状态。不同的是,poll底层使用链表存储socket号,没有最大连接数的限制,但同样存在遍历所有socket和重复传递数据的问题 [^3]。 - **epoll**:是一种更为高效的IO多路复用实现方式,它使用事件驱动的机制,只关注有事件发生的socket,避免了遍历所有socket,并且使用内存映射(mmap)技术避免了数据的重复复制,提高了效率。 ### 示例代码(Java中使用NIO模拟简单的IO多路复用) ```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; import java.util.Set; public class NioServer { public static void main(String[] args) throws IOException { // 创建选择器 Selector selector = Selector.open(); // 打开监听通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 绑定端口 serverSocketChannel.socket().bind(new InetSocketAddress(8080)); // 设置为非阻塞模式 serverSocketChannel.configureBlocking(false); // 将通道注册到选择器上,并监听连接事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { // 等待事件发生 int readyChannels = selector.select(); if (readyChannels == 0) continue; // 获取所有就绪的选择键 Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { // 处理新的连接 ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = serverChannel.accept(); socketChannel.configureBlocking(false); // 注册读事件 socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { // 处理读事件 SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = socketChannel.read(buffer); if (bytesRead > 0) { buffer.flip(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); System.out.println("Received: " + new String(data)); } } // 移除处理过的键 keyIterator.remove(); } } } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值