day10-epoll IO多路复用实现单线程支持上万并发代码实例

select的不足

尽管select用起来挺爽,跨平台的特性。但是select还是存在一些问题。
select需要遍历监视的文件描述符,并且这个描述符的数组还有最大的限制。随着文件描述符数量的增长,用户态和内核的地址空间的复制所引发的开销也会线性增长。即使监视的文件描述符长时间不活跃了,select还是会线性扫描。

为了解决这些问题,操作系统又提供了poll方案,但是poll的模型和select大致相当,只是改变了一些限制。目前Linux最先进的方式是epoll模型,它使用的是selectors模块。

许多高性能的软件如nginxnodejs都是基于epoll进行的异步。

selectors模块实现I/O多路复用机制

这个模块允许高层高效的I/O多路复用,建立在select模块的原函数基础之上。

selectors实例

#服务器端
import selectors  #基于select模块实现的IO多路复用
import socket

sel = selectors.DefaultSelector()  #默认选择器使用在当前平台上可以的最有效的实现


def accept(sock, mask):
"""建立连接,然后交给read函数接收从客户端来的数据(接收数据注册EVENT_READ事件)"""
    conn, addr = sock.accept()  # Should be ready
    print('accepted', conn, 'from', addr)
    conn.setblocking(False)  #非阻塞模式
    #根据平台选择最佳的IO多路机制,比如Linux就会选择epoll
    sel.register(conn, selectors.EVENT_READ, read)  #新连接注册read回调函数


def read(conn, mask):
"""接收从客户端来的数据,然后判断需要的操作"""
    data = conn.recv(1000)  # Should be ready
    if data:
        print('echoing', repr(data), 'to', conn)
        conn.send(data)  # Hope it won't block
    else:
        print('closing', conn)
        #接收到空数据就从事件列表中注销conn的事件,同时关闭conn
        sel.unregister(conn)
        conn.close()


sock = socket.socket()
sock.bind(('localhost', 10000))
sock.listen(100)
sock.setblocking(False)
#注册事件,只要有连接来就调用accept函数
sel.register(sock, selectors.EVENT_READ, accept)

while True:
    # [(sock),(),()]监听EVENT事件列表
    events = sel.select()  #默认阻塞。有活动的事件就交给相应的方法处理
    print("事件:",events)
    for key, mask in events:
        #print(key.data)  #accept  找出有活动的绑定函数
        #print(key.fileobj) #sock  找出有活动的文件描述符
        callback = key.data #1 accept(sock,mask) 2 read(conn,mask)
        callback(key.fileobj, mask)  #key.fileobj = 注册的文件句柄(对象)

#运行输出

事件: [(SelectorKey(fileobj=<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998)>, fd=4, events=1, data=<function accept at 0x10403aa60>), 1)]
accepted <socket.socket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998), raddr=('127.0.0.1', 55795)> from ('127.0.0.1', 55795)
事件: [(SelectorKey(fileobj=<socket.socket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998), raddr=('127.0.0.1', 55795)>, fd=7, events=1, data=<function read at 0x104506488>), 1)]
echoing b'1' to <socket.socket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998), raddr=('127.0.0.1', 55795)> 

程序详解 

生成一个对象

sel = selectors.DefaultSelector()  

注册事件

sel.register(sock, selectors.EVENT_READ, accept)

循环事件

while True:
    # [(sock),(),()]监听
    events = sel.select()  #默认阻塞。有活动连接就返回活动的连接列表
    print("事件:",events)
    for key, mask in events:
        #print(key.data)   #accept  找出有活动的绑定函数
        #print(key.fileobj) #sock  找出有活动的文件描述符
        callback = key.data #第1次调用accept(sock,mask), 第2次调用read(conn,mask)
        callback(key.fileobj, mask)  #key.fileobj = 注册的文件句柄(对象)

多并发客户端

#客户端
import socket
import sys

messages = [ b'This is the message. ',
             b'It will be sent ',
             b'in parts.',
             ]
server_address = ('localhost', 10000)

# 创建3个tcp/ip的socket连接
socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(3)]

# 将socket连接到服务器正在监听的端口
print('connecting to %s port %s' % server_address)
for s in socks:
    s.connect(server_address)

for message in messages:

    #在2个socket上发送信息
    for s in socks:
        print('%s: sending "%s"' % (s.getsockname(), message) )
        s.send(message)

    #读取2个socket上的response
    for s in socks:
        data = s.recv(1024)
        print( '%s: received "%s"' % (s.getsockname(), data) )
        if not data:
            print('closing socket', s.getsockname() )

#运行输出
connecting to localhost port 10000
('127.0.0.1', 56849): sending "b'This is the message. '"
('127.0.0.1', 56850): sending "b'This is the message. '"
('127.0.0.1', 56851): sending "b'This is the message. '"
('127.0.0.1', 56849): received "b'This is the message. '"
('127.0.0.1', 56850): received "b'This is the message. '"
('127.0.0.1', 56851): received "b'This is the message. '"
('127.0.0.1', 56849): sending "b'It will be sent '"
('127.0.0.1', 56850): sending "b'It will be sent '"
('127.0.0.1', 56851): sending "b'It will be sent '"
('127.0.0.1', 56849): received "b'It will be sent '"
('127.0.0.1', 56850): received "b'It will be sent '"
('127.0.0.1', 56851): received "b'It will be sent '"
('127.0.0.1', 56849): sending "b'in parts.'"
('127.0.0.1', 56850): sending "b'in parts.'"
('127.0.0.1', 56851): sending "b'in parts.'"
('127.0.0.1', 56849): received "b'in parts.'"
('127.0.0.1', 56850): received "b'in parts.'"
('127.0.0.1', 56851): received "b'in parts.'"

 

posted @ 2017-12-16 20:36  Mr.hu  阅读(487)  评论(0编辑  收藏  举报