目录

  • 参考源
  • 介绍
  • Windows上使用select接口
  • 基本工作原理
  • 额外说明
  • 该模型的优点
  • 该模型的缺点
  • 实现
  • Server端
  • Client端


参考源

  • cai128118*
  • 老男孩教育林海峰(Egon)老师

介绍

操作系统提供了三种IO多路复用机制:

  1. select
  2. poll
  3. epoll

在Windows系统上, 只支持select

在类Unix系统上, 支持以上三种

这其中, select和poll并不适用于大并发, 而epoll适用于大并发.

select

  • 内部数据结构: 列表
  • 检测方法: 轮询
  • 检测数量: 有限

poll

  • 数据结构: 不是列表
  • 检测方法: 轮询
  • 检测数量: 更广

epoll 检测方式并不是轮询, 而是给每个检测的对象添加了回调函数

Windows上使用select接口

基本工作原理

当用户进程调用select之后, 整个进程会阻塞,
而同时, kernel会监视所有select负责的socket对象.
当任何一个socket对象数据准备好时,kernel就会返回,
用户进程再执行拿数据的操作, 将数据拷贝至用户进程内存中.

额外说明

强调:

  1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。
    select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
  2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,
    但是,整个用户的进程其实是一直被block的。
    只不过用户进程是被select这个函数block,而不是被socket IO给block。

结论: select的优势在于可以处理多个连接,不适用于单个连接

该模型的优点

相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,
同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

该模型的缺点

  1. 首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,
    select()接口本身需要消耗大量时间去轮询各个句柄。很多操作系统提供了更为高效的接口,
    如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。如果需要实现更高效的服务器程序,类似epoll这样的接口更被推荐。遗憾的是不同的操作系统特供的epoll接口有很大差异,
    所以使用类似于epoll的接口实现具有较好跨平台能力的服务器会比较困难。
  2. 其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。

实现

Server端

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""IO多路复用

Server端

IO多路复用:
    操作系统提供了三种IO多路复用机制:
        1. select
        2. poll
        3. epoll
    在Windows系统上, 只支持select
    在类Unix系统上, 支持以上三种
    
    这其中, select和poll并不适用于大并发, 而epoll适用于大并发.
    
    select 内部数据结构: 列表
           检测方法: 轮询
           检测数量: 有限
    
    poll  数据结构: 不是列表
          检测方法: 轮询
          检测数量: 更广
          
    epoll 检测方式并不是轮询, 而是给每个检测的对象添加了回调函数 
    
    Windows上使用select接口
    基本工作原理:
        当用户进程调用select之后, 整个进程会阻塞, 而同时, kernel会监视
        所有select负责的socket对象. 当任何一个socket对象数据准备好时,
        kernel就会返回, 用户进程再执行拿数据的操作, 将数据拷贝至用户进程内存中.


    强调:
        1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。
        select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

        2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,
          但是,整个用户的进程其实是一直被block的。
         只不过用户进程是被select这个函数block,而不是被socket IO给block。

    结论: select的优势在于可以处理多个连接,不适用于单个连接
    
   该模型的优点:
      相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,
      同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

   该模型的缺点:
     首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,
   select()接口本身需要消耗大量时间去轮询各个句柄。很多操作系统提供了更为高效的接口,
   如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。如果需要实现更高效的服务器程序,
   类似epoll这样的接口更被推荐。遗憾的是不同的操作系统特供的epoll接口有很大差异,
   所以使用类似于epoll的接口实现具有较好跨平台能力的服务器会比较困难。

     其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。
"""

import select
import socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sk.bind(('', 8080))

sk.listen(32)

sk.setblocking(False)

rlist = list([sk, ])

wlist = list()

xlist = list()

wdict = dict()

while 1:
    rl, wl, xl = select.select(rlist, wlist, xlist, None)
    for sock in rl:
        if sock == sk:
            conn, addr = sock.accept()
            rlist.append(conn)
        else:
            try:
                recvs = sock.recv(1024)
                if not recvs:
                    sock.close()
                    rlist.remove(sock)
                print(recvs.decode(encoding='utf-8'))
                wlist.append(sock)
                wdict[sock] = recvs + b'_suffix'
            except ConnectionResetError:
                sock.close()
                rlist.remove(sock)

    for sock in wl:
        sock.sendall(wdict.get(sock))
        wdict.pop(sock)
        wlist.remove(sock)

Client端

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""IO多路复用

Client端
"""

import socket
import threading


def my_client():
    sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
    sk.connect(('localhost', 8080))
    while 1:
        sk.send(b'hi')
        msg = sk.recv(1024)
        print(msg)


for i in range(10):
    threading.Thread(target=my_client).start()