IO多路复用

IO多路复用 -利用内置模块select[Windows Linux]

  • 循环每一个被监听的项目,看看是否有读写错误操作
  • 所以随着监听项目的增多,效率将变差
  • 服务器端
  • select必须传入三个参数分别是三个列表read_list, write_list, erro_list返回值是一个元祖对应传入的参数

      #select帮助感知某个IO操作是否有变动
      #read开始被read
      #write开始被write
      #erro开始有erro
      #一但感知到就会返回响应的参数
    
    
      import socket
      from select import select
    
      sk = socket.socket()
      address = ('127.0.0.1', 8080)
      sk.bind(address)
      sk.setblocking(False)
      sk.listen() #监听
    
      read_ls = [sk]  #将sk对象加入一个列表
    
      while 1:
          r_ls, w_ls, x_ls = select(read_ls, [], [])
          for i in r_ls:
              if i is sk:
                  conn, addr = i.accept()
                  read_ls.append(conn)
              else:
                  ret = i.recv(1024)  #接收消息
                  if ret == b'':
                      read_ls.remove(i)
                      i.close()
                      continue
                  print(ret)
                  i.send(b'byebye!')  #发送消息
  • 客户端 -起多线程的客户端

      import socket
      from threading import Thread
    
      def func():
          sk = socket.socket()
          address = ('127.0.0.1', 8080)
          sk.connect(address)
          sk.send(b'hello')
          ret = sk.recv(1024)
          print(ret)
          sk.close()
    
      t_ls = []
      for i in range(10):
          t = Thread(target=func)
          t.start()    

其他类似的模块

poll -[Linux]

  • select机制基本上一样
  • 但是poll监听的对象比select监听上限多

epol -[Linux][Windows上没有 ]

  • 高端的
  • 并不是循环每一个项目进行监听
  • 而是为每一个项目增加回调函数
  • 有信号来直接进行回调函数,所以效率比循环高
### 原理 在传统的阻塞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(); } } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值