python(网络编程-IO多路复用)

                                                  IO多路复用

 

一、常见的 IO 模型

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:

        (1) 同步阻塞IO(Blocking IO):即传统的IO模型。

        (2) 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。

        (3) IO多路复用(IO MulTIplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。

        (4) 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。

    同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

    阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。

 

二、IO 多路复用

    IO多路复用是一种系统调用,内核能够同时对多个IO描述符进行就绪检查。当所有被监听的IO都没有就绪时,调用将阻塞;当至少有一个IO描述符就绪时,调用将返回,用户代码可通过检查究竟是哪个IO就绪来进一步处理业务。显然,IO多路复用是解决系统里面存在N个IO描述符的问题的,这里必须明确IO复用和IO阻塞与否并不是一个概念,IO复用只检测IO是否就绪(读就绪或者写就绪等),具体的数据的输入输出还是需要依靠具体的IO操作完成(阻塞操作或非阻塞操作)。最典型的IO多路复用技术有selectpollepoll等。select具有最大数量描述符限制,而epoll则没有,并且在机制上,epoll也更为高效。select的优势仅仅是跨平台支持性,所有平台和较低版本的内核都支持select模式,epoll则不是。

    在IO相关的编程中,IO复用起到的作用相当于一个阀门,让后续IO操作更为精准高效。

 

三、编程模型

    在进行实际的Socket编程的时候,无论是客户端还是服务端,大致有几种模式可以选择:

        1、阻塞式。纯采用阻塞式,这种方式很少见,基本只会出现在demo中。多个描述符需要用多个进程或者线程来一一对应处理。

        2、非阻塞式。纯非阻塞式,对IO的就绪与否需要在用户空间通过轮询来实现。

        3、IO多路复用+阻塞式。仅使用一个线程就可以实现对多个描述符的状态管理,但由于IO输入输出调用本身是阻塞的,可能出现某个IO输入输出过慢,影响其他描述符的效率,从而体现出整体性能不高。此种方式编程难度比较低。

        4、IO多路复用+非阻塞式。在多路复用的基础上,IO采用非阻塞式,可以大大降低单个描述符的IO速度对其他IO的影响,不过此种方式编程难度较高,主要表现在需要考虑一些慢速读写时的边界情况,比如读黏包、写缓冲不够等。

 

四、示例    

import selectors
import socket

# selectors模块默认会用epoll,如果你的系统中没有epoll(比如windows)则会自动使用select
sel = selectors.DefaultSelector()  # 生成一个select对象


def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print('accepted', conn, 'from', addr)
    conn.setblocking(False)  # 设定非阻塞
    sel.register(conn, selectors.EVENT_READ, read)  # 新连接注册read回调函数


def read(conn, mask):
    data = conn.recv(1024)  # Should be ready
    if data:
        print('echoing', repr(data), 'to', conn)
        conn.send(data)
    else:
        print('closing', conn)
        sel.unregister(conn)
        conn.close()


sock = socket.socket()
sock.bind(('127.0.0.1', 8000))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)  # 把刚生成的sock连接对象注册到select连接列表中,并交给accept函数处理

while True:
    events = sel.select()  # 默认是阻塞,有活动连接就返回活动的连接列表
    # 这里看起来是select,其实有可能会使用epoll,如果你的系统支持epoll,那么默认就是epoll
    for key, mask in events:
        callback = key.data  # 去调accept函数
        callback(key.fileobj, mask)  # key.fileobj就是readable中的一个socket连接对象

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值