Python下使用IO多路复用(入门篇)

IO操作是不占用CPU的,IO多路复用,是要管理起所有的IO操作。IO多路复用的典型场景是监听socket对象内部是否发生了变化

socket内部什么时候会有变化:

  • 建立连接
  • 发送消息

socket实例

这里使用socket实现一个简单的Echo Server的功能

  • <font color="red">server.py</font>
import socket

# 创建socket对象,绑定IP端口,监听
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)

# 循环接受每一个连接池中的连接
while True:
    # 接受客户端连接
    conn, address = sk.accept()
    # 向客户端发送欢迎消息
    conn.sendall(bytes('hello', encoding='utf8'))

    # 进入到收发消息的循环中
    while True:

        # Windows客户端在异常断开后抛出异常,这里是处理Windows的断开情况
        try:
            recv = conn.recv(1024)

            # Linux客户端断开recv会是空值,这里处理Linux的断开情况
            if not recv:
                break

            # 这里处理客户端主动发出断开请求的情况
            if str(recv, encoding='utf-8') == 'exit':
                break
        except Exception as ex:
            break

        # 向客户端发送数据
        conn.sendall(recv)
  • <font color="blue">client.py</font>
import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 1559))
# 接收欢迎消息
data = sk.recv(1024)
print(data)

while True:
    i = input("> ")
    # 向服务端发送消息
    sk.sendall(bytes(i, encoding='utf8'))

    # 接收服务端发来的消息
    msg = sk.recv(1024)
    print(str(msg, encoding='utf-8'))

sk.close()

以上的socket代码同一时间仅能处理一个客户端的请求,之后连接上来的客户端在第一个客户端还没有断开的时候,会一直等待,直到上一个客户端的请求断开

select.selext()中的第一个参数

建立连接

上面说到,一种socket会变的情况是建立连接

上面的代码中,涉及到建立连接的socket是sk对象(变量)sk对象在执行到sk.accept()时,接受了一个新客户端的连接请求时候,socket内部就发生了变化,我们就需要监听这种变化,用以分辨出新的客户端的连接

创建socket,绑定并监听之后socket一般不会发生变化,只有当有新的客户端连接进来的时候,socket才会发生变化,我们需要监听的也是这个阶段的变化

得出结论:当socket被创建、绑定并监听之后发生变化,就是有新的客户端进行连接

  • <font color="red">server.py</font>
import socket
import select

# 创建socket对象,绑定IP端口,监听
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)

while True:
    rList, w, e = select.select([sk,], [], [], 1)
    print(rList)

上面代码引入了IO多路复用中的select模型,使用select.select()方法,会返回一个有三个元素的元祖

在select.select()方法中的第一个参数,暂时只添加了一个服务端的socket对象,只要服务端socket对象sk有变化(新的客户端连接)就立即把变化的socket对象加入到rList列表中

监听的socket列表中,sk对象有变化 ---> rList = [sk]

监听的socket列表中,没有socket发生变化 ---> rList = []

以上代码跑起来的效果:

[]
[]
[]
...
# 每秒打印一个[]
# select.sekect()中的第四个参数起了作用
# 超时时间,监听的对象没有发生变化的时候,多少秒循环一次

说明没有新的连接请求,下面将试验有客户端连接产生的情况

  • <font color="blue">client.py</font>
import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 1559))
sk.close()

客户端一旦连接到服务端,服务端的回显如下

[<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1559)>]
[<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1559)>]
[<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1559)>]
...
# 疯狂的快速打印...

从上面可以看出,有一个客户端连接进来了。服务端的sk对象内部发生了变化,当select监听到sk对象发生变化后,立即将发生变化的对象赋值给了rList列表(append到列表)从打印出来的内容中可以看出,列表中的元素是发生变化的socket对象

处理rList(服务端socket)

rList中保存了所有发生变化的socket对象,以上代码中只监听了服务端socket对象,这里暂时只讨论服务端socket变化的情况

  • <font color="red">server.py</font>
import socket
import select

# 创建socket对象,绑定IP端口,监听
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)

while True:
    # 监听服务端socket对象sk
    rList, w, e = select.select([sk,], [], [], 1)
    print(rList)
    
    # 遍历rList中的每一个socket对象
    # 目前rList中只会出现服务端的socket对象
    for s in rList:
        conn, address = s.accept()
        conn.sendall(bytes('hello', encoding='utf8'))
  • <font color="blue">client.py</font>
import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 1559))
data = sk.recv(1024)
print(data)

while True:
    input("> ")

sk.close()

连续运行三个客户端连接时,服务端的回显:

[]
[]
[<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1559)>]
[]
[<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1559)>]
[]
[<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1559)>]
[]
[]

每个客户端的回显:

b'hello'
>

以上通过对rList中服务端socket对象执行accept()方法,来实现了一个类似并发连接的效果,每一个连接进来的客户端都会被服务端接受请求,“同时”提供服务

接收客户端消息

上面提到,不仅创建连接会触发socket的变化,与客户端连接建立后,客户端发来消息,也会引发客户端socket连接的内部变化

  • <font color="red">server.py</font>
import socket
import select

# 创建socket对象,绑定IP端口,监听
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)

inputs = [sk]
while True:
    rList, w, e = select.select(inputs, [], [], 1)
    print(rList)

    for s in rList:
        conn, address = s.accept()
        # conn也是一个socket对象
        # 当服务端socket接收到客户的请求后,会分配一个新的socket对象专门用来和这个客户端进行连接通信
        
        # 当服务端分配新的socket对象给新连接进来的客户端的时候
        # 我们也需要监听这个客户端的socket对象是否会发生变化
        # 一旦发生变化,意味着客户端向服务器端发来了消息
        inputs.append(conn)
        conn.sendall(bytes('hello', encoding='utf8'))

在上面的代码中,我把服务端socket为新客户端创建的socket也加入到了监听列表中,那么如果有客户端发来消息,select监听到客户端socket(conn)发生变化并加入到rList列表中后,在for循环处理中,客户端的socket并没有accept()方法,而且也不要这个方法,这就需要在for循环中对两类socket区分对待

import socket
import select

# 创建socket对象,绑定IP端口,监听
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)

inputs = [sk]
while True:
    rList, w, e = select.select(inputs, [], [], 1)
    print("select当前监听socket对象的数量>", len(inputs), " | 发生变化的socket数量>", len(rList))

    for s in rList:
        # 判断socket对象如果是服务端的socket对象的话
        if s == sk:
            conn, address = s.accept()
            # conn也是一个socket对象
            # 当服务端socket接收到客户的请求后,会分配一个新的socket对象专门用来和这个客户端进行连接通信

            # 当服务端分配新的socket对象给新连接进来的客户端的时候
            # 我们也需要监听这个客户端的socket对象是否会发生变化
            # 一旦发生变化,意味着客户端向服务器端发来了消息
            inputs.append(conn)
            conn.sendall(bytes('hello', encoding='utf8'))
        # 其他的就都是客户端的socket对象了
        else:
            # 意味着客户端给服务端发送消息了
            msg = s.recv(1024)
            print(msg)
  • <font color="blue">client.py</font>
import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 1559))
# 接收欢迎消息
data = sk.recv(1024)
print(data)

while True:
    i = input("> ")
    # 向服务端发送消息
    sk.sendall(bytes(i, encoding='utf8'))

sk.close()

当我运行一个客户端,并Ctrl+C退出时,服务端回显界面在疯狂的打印消息。问题出在了服务端监听的客户端socket连接,当客户端与服务端断开连接时,应在服务端select监听socket对象列表中将该客户端socket对象移除

  • <font color="red">server.py</font>
import socket
import select

# 创建socket对象,绑定IP端口,监听
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)

inputs = [sk]
while True:
    rList, w, e = select.select(inputs, [], [], 1)
    print("select当前监听socket对象的数量>", len(inputs), " | 发生变化的socket数量>", len(rList))

    for s in rList:
        # 判断socket对象如果是服务端的socket对象的话
        if s == sk:
            conn, address = s.accept()
            # conn也是一个socket对象
            # 当服务端socket接收到客户的请求后,会分配一个新的socket对象专门用来和这个客户端进行连接通信

            # 当服务端分配新的socket对象给新连接进来的客户端的时候
            # 我们也需要监听这个客户端的socket对象是否会发生变化
            # 一旦发生变化,意味着客户端向服务器端发来了消息
            inputs.append(conn)
            conn.sendall(bytes('hello', encoding='utf8'))
        # 其他的就都是客户端的socket对象了
        else:
            try:
                # 意味着客户端给服务端发送消息了
                msg = s.recv(1024)

                # Linux平台下的处理
                if not msg:
                    raise Exception('客户端已断开连接')
                print(msg)
            except Exception as ex:
                # Windows平台下的处理
                inputs.remove(s)

使用最新的server.py与client.py进行测试时,依次运行多个客户端,再依次关闭多个客户端,服务端的回显如下:

select当前监听socket对象的数量> 1  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 1  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 1  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 1  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 1  | 发生变化的socket数量> 1
select当前监听socket对象的数量> 2  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 2  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 2  | 发生变化的socket数量> 1
select当前监听socket对象的数量> 3  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 3  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 3  | 发生变化的socket数量> 1
select当前监听socket对象的数量> 4  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 4  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 4  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 4  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 4  | 发生变化的socket数量> 1
b''
select当前监听socket对象的数量> 3  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 3  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 3  | 发生变化的socket数量> 1
b''
select当前监听socket对象的数量> 2  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 2  | 发生变化的socket数量> 1
b''
select当前监听socket对象的数量> 1  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 1  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 1  | 发生变化的socket数量> 0
select当前监听socket对象的数量> 1  | 发生变化的socket数量> 0

特别注意:这里的服务端定义,当我收到客户端发来的空值的时候,我就默认认为客户端主动需要断开与服务端的连接。由于服务端的这个默认规则,在写客户端的时候,一定要注意处理客户端输入的值为空的情况

给客户端回复消息

  • <font color="red">server.py</font>
import socket
import select

# 创建socket对象,绑定IP端口,监听
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)

inputs = [sk]
while True:
    rList, w, e = select.select(inputs, [], [], 1)
    print("select当前监听socket对象的数量>", len(inputs), " | 发生变化的socket数量>", len(rList))

    for s in rList:
        # 判断socket对象如果是服务端的socket对象的话
        if s == sk:
            conn, address = s.accept()
            # conn也是一个socket对象
            # 当服务端socket接收到客户的请求后,会分配一个新的socket对象专门用来和这个客户端进行连接通信

            # 当服务端分配新的socket对象给新连接进来的客户端的时候
            # 我们也需要监听这个客户端的socket对象是否会发生变化
            # 一旦发生变化,意味着客户端向服务器端发来了消息
            inputs.append(conn)
            conn.sendall(bytes('hello', encoding='utf8'))
        # 其他的就都是客户端的socket对象了
        else:
            try:
                # 意味着客户端给服务端发送消息了
                msg = s.recv(1024)

                # Linux平台下的处理
                if not msg:
                    raise Exception('客户端已断开连接')
                print(msg)
                
                # 向客户端回复消息
                # 这种写法是完全可以的,但是缺点是读写都混在了一起
                s.sendall(msg)
            except Exception as ex:
                # Windows平台下的处理
                inputs.remove(s)

但是一般情况下,会做读写分离,可以通过select,实现读写分离(收发分离)

select.selext()中的第二个参数

rList, wList, e = select.select([], [], [], 1)

select.select()的第二个参数有什么值,wList中就会有什么值

利用select的这个特性,可以把需要回复消息的客户端socket对象赋值给select的第二个参数

import socket
import select

# 创建socket对象,绑定IP端口,监听
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)

inputs = [sk]
outputs = []
while True:
    rList, wList, e = select.select(inputs, outputs, [], 1)
    print("---" * 20)
    print("select当前监听inputs对象的数量>", len(inputs), " | 发生变化的socket数量>", len(rList))
    print("select当前监听outputs对象的数量>", len(outputs), " | 需要回复客户端消息的数量>", len(wList))

    # 遍历rList(建立连接和接收数据)
    for s in rList:
        # 判断socket对象如果是服务端的socket对象的话
        if s == sk:
            conn, address = s.accept()
            # conn也是一个socket对象
            # 当服务端socket接收到客户的请求后,会分配一个新的socket对象专门用来和这个客户端进行连接通信

            # 当服务端分配新的socket对象给新连接进来的客户端的时候
            # 我们也需要监听这个客户端的socket对象是否会发生变化
            # 一旦发生变化,意味着客户端向服务器端发来了消息
            inputs.append(conn)
            conn.sendall(bytes('hello', encoding='utf8'))
        # 其他的就都是客户端的socket对象了
        else:
            try:
                # 意味着客户端给服务端发送消息了
                msg = s.recv(1024)

                # Linux平台下的处理
                if not msg:
                    raise Exception('客户端已断开连接')
                else:
                    outputs.append(s)
                    print(msg)

                # 向客户端回复消息
                # 这种写法是完全可以的,但是缺点是读写都混在了一起
                # s.sendall(msg)
            except Exception as ex:
                # Windows平台下的处理
                inputs.remove(s)

    # 遍历wList(遍历给服务端发送过消息的客户端)
    for s in wList:

        # 给所有的客户端统一回复内容
        s.sendall(bytes('server response', encoding='utf8'))

        # 回复完成后,一定要将outputs中该socket对象移除
        outputs.remove(s)

wList = 所有给服务端发送消息的客户端,也是需要回复消息客户端列表

  • <font color="blue">client.py</font>
import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 1559))
# 接收欢迎消息
data = sk.recv(1024)
print(data)

while True:
    i = input("> ")
    # 向服务端发送消息
    sk.sendall(bytes(i, encoding='utf8'))

    # 接收服务端发来的消息
    msg = sk.recv(1024)
    print(str(msg, encoding='utf-8'))

sk.close()

执行过程:

  • 依次连接三个客户端
  • 第一个客户端依次向服务发送了两次消息

服务端的回显:

------------------------------------------------------------
select当前监听inputs对象的数量> 1  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 1  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 2  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 2  | 发生变化的socket数量> 1
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 3  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 3  | 发生变化的socket数量> 1
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 4  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 4  | 发生变化的socket数量> 1
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
b'ps'
------------------------------------------------------------
select当前监听inputs对象的数量> 4  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 1  | 需要回复客户端消息的数量> 1
------------------------------------------------------------
select当前监听inputs对象的数量> 4  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 4  | 发生变化的socket数量> 1
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
b'ps'
------------------------------------------------------------
select当前监听inputs对象的数量> 4  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 1  | 需要回复客户端消息的数量> 1
------------------------------------------------------------
select当前监听inputs对象的数量> 4  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 3  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 3  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 2  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 2  | 发生变化的socket数量> 1
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0
------------------------------------------------------------
select当前监听inputs对象的数量> 1  | 发生变化的socket数量> 0
select当前监听outputs对象的数量> 0  | 需要回复客户端消息的数量> 0

第一个客户端的回显:

b'hello'
> ps
server response
> ps
server response
>

以上代码实现了简单的收发消息的分离,现在又多了一点需求,目前所有给服务端发送消息的客户端,服务端都统一回复了相同的内容,现在要服务端实现Echo Server的功能,即客户端发送什么消息,服务端就给客户端回复什么消息

import socket
import select

# 创建socket对象,绑定IP端口,监听
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)

inputs = [sk]
outputs = []
messages = {}

"""
messages = {
    socket_obj1: [msg]
    socket_obj2: [msg]
}
"""

while True:
    rList, wList, e = select.select(inputs, outputs, [], 1)
    print("---" * 20)
    print("select当前监听inputs对象的数量>", len(inputs), " | 发生变化的socket数量>", len(rList))
    print("select当前监听outputs对象的数量>", len(outputs), " | 需要回复客户端消息的数量>", len(wList))

    # 遍历rList(建立连接和接收数据)
    for s in rList:
        # 判断socket对象如果是服务端的socket对象的话
        if s == sk:
            conn, address = s.accept()
            # conn也是一个socket对象
            # 当服务端socket接收到客户的请求后,会分配一个新的socket对象专门用来和这个客户端进行连接通信

            # 当服务端分配新的socket对象给新连接进来的客户端的时候
            # 我们也需要监听这个客户端的socket对象是否会发生变化
            # 一旦发生变化,意味着客户端向服务器端发来了消息
            inputs.append(conn)

            # 在messages中为该对象创建key
            messages[conn] = []

            conn.sendall(bytes('hello', encoding='utf8'))
        # 其他的就都是客户端的socket对象了
        else:
            try:
                # 意味着客户端给服务端发送消息了
                msg = s.recv(1024)

                # Linux平台下的处理
                if not msg:
                    raise Exception('客户端已断开连接')
                else:
                    outputs.append(s)
                    messages[s].append(msg)

                # 向客户端回复消息
                # 这种写法是完全可以的,但是缺点是读写都混在了一起
                # s.sendall(msg)
            except Exception as ex:
                # Windows平台下的处理
                inputs.remove(s)

                # 在客户端断开连接后,相对应的该客户端的messages中的信息也需要删除
                del messages[s]

    # 遍历wList(遍历给服务端发送过消息的客户端)
    for s in wList:

        # 在该客户端连接对象的messages信息中取出一个进行回复
        msg = messages[s].pop()

        # 根据客户端发来的消息进行回复
        s.sendall(msg)

        # 回复完成后,一定要将outputs中该socket对象移除
        outputs.remove(s)

服务端做出以上修改,客户端不需要改变

  • 11行 为了让一个客户端socket对象收消息和发消息产生关联,引入了一个新的全局变量messages
  • 40行 在新客户端连接进来的时候,就预先为该socket对象在messages中创建对应的key
  • 54行 在该对象中添加消息
  • 64行 在客户端关闭连接收,清理该对象的消息列表
  • 70行 将该对象在第40行插入的消息中取出来

总结

使用IO多路复用,实际上实现了类似并发效果的伪并发。内部实际使用了循环来高效的处理阻塞请求

Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。

  • Windows Python:提供: select
  • Mac Python:提供: select
  • Linux Python:提供: select、poll、epoll

注意:网络操作、文件操作、终端操作等均属于IO操作,对于windows只支持Socket操作,其他系统支持所有IO操作,但是无法检测普通文件操作自动上次读取是否已经变化

对于select方法:

句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)

 
参数: 可接受四个参数(前三个必须)
返回值:三个列表
 
select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。
1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中
2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中
3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中
4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化
   当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。

附加:select poll epoll的区别

IO多路复用是系统内核实现的,系统内部维护了一个for循环,一个一个地去检测对象是否有变化

首先需要明确一点的是,for循环的效率是不高的

IO多路复用种类 实现原理 监听对象个数
select 系统内部维护了一个for循环 1024
poll 系统内部维护了一个for循环 没有限制
epoll 句柄序列发生变化时自动通知epoll 没有限制
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容