IO多路复用

IO多路复用用在单线程里,实现并发效果。但其实属于同步IO范畴,是伪并发

常用于网络通信,和socket搭配使用
第一种:

import socket
import select
import time
sk=socket.socket()
sk.bind(("127.0.0.1",9904))
sk.listen(5)
while True:
    r,w,e=select.select([sk,],[],[],5)  #([监听的输入],[监听的输入],[监听的reeor],[轮循监视的时间间隔],)

    print("轮循")
    for i in r:
        conn,add=i.accept()
        print(conn)
        print("hello")
    print('干其他事')
    time.sleep(5)

实现了只能允许一个客户端连接,循环聊天

'''
上面的代码中
 ???在有一个客户端连接的时候,为什么如果不调用accept,会反复print?

很多答案说是什么select是水平触发方式,不太容易理解

首先一个客户端连接之后,内核态里有了一个sk的双向链接,如果不执行它的accept方法,双向链接一直存在于内核态,未被复制及删除,所以select每次轮循都会执行for循环,自然一直print,传输数据同理
 '''

第二种方法:

import socket
import select
import time
sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.setblocking(False)
sk.bind(("127.0.0.1",8801))
sk.listen(5)
inputs=[sk,]
while True:
    r,w,e=select.select(inputs,[],[],5)
    for obj in r:
        if obj==sk:
            conn,add=obj.accept()
            inputs.append(conn)
        else:
            data_byte=obj.recv(1024)
            print(str(data_byte,'utf8'))
            inp=input('回答%s号客户>>>'%inputs.index(obj))
            obj.sendall(bytes(inp,'utf8'))
            
    print("这里可以做其他事情")

能允许back_lock个客户端连接,也能和多个客户端循环聊天,只是一个客户端请求到达处理完毕后才能处理下一个,是一种同步模式(这里用input来模拟同步效果)

以上都用到select,除此之外,poll和epoll也可以,性能更优

第三种方法:

import selectors
import socket
sel = selectors.DefaultSelector()       #根据操作系统选择最优IO多路复用方式
def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)
def read(conn, mask):
    try:
        data = conn.recv(1024)  # Should be ready
        conn.send(data)  # Hope it won't block
    except Exception:
        sel.unregister(conn)
        conn.close()
sock = socket.socket()
sock.bind(('localhost', 8090))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)  #注册,监听对象和监听对象有变化后返回值要执行的函数
while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data       #key.date是监视对象有变化时返回的函数地址
        callback(key.fileobj, mask)   #key。fileobj是返回监视的有变化的对象

借助封装select的socketserver库来实现和第二中相同的功能

相关资料:
​网络 IO 演变发展过程和模型介绍
你管这破玩意叫 IO 多路复用?
百万 Go TCP 连接的思考

### 原理 在传统的阻塞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、付费专栏及课程。

余额充值