一、前言
之前我们就讲了select的这种方式,使用的是轮询方式去监测客户端的连接,效率比较低下,我们今天来聊聊epoll的方式,这种效率更高,但是这种方式在Windows下不支持,在Linux是支持的,那就不得不说下面的一个模块selectors。
二、selectors模块
2.1、英文解释
This module allows high-level and efficient I/O multiplexing, built upon the select
module primitives. Users are encouraged to use this module instead, unless they want precise control over the OS-level primitives used.
2.2、中文解释
selectors 默认是epoll,如果找不到epoll的话,比如说windows操作系统,就会找select
三、selectors模块使用
3.1、selectors实现非阻塞连接
说明:默认它是阻塞的,只要不阻塞了,就代表肯定有活动的数据,我就循环这个events。
import selectors,socket
sel = selectors.DefaultSelector()
def accept(sock,mask):
"接收客户端信息实例"
conn,addr = sock.accept()
print("accepted",conn,'from',addr)
conn.setblocking(False)
sel.register(conn,selectors.EVENT_READ,read) #新连接注册read回调函数
def read(conn,mask):
"接收客户端的数据"
data = conn.recv(1024)
if data:
print("echoing",repr(data),'to',conn)
conn.send(data)
else:
print("closing",conn)
sel.unregister(conn)
conn.close()
server = socket.socket()
server.bind(('localhost',9999))
server.listen(500)
server.setblocking(False)
sel.register(server,selectors.EVENT_READ,accept) #注册事件,只要来一个连接就调accept这个函数,
#sel.register(server,selectors.EVENT_READ,accept) == inputs=[server,]
while True:
events = sel.select() #这个select,看起来是select,有可能调用的是epoll,看你操作系统是Windows的还是Linux的
#默认阻塞,有活动连接就返回活动连接列表
print("事件:",events)
for key,mask in events:
callback = key.data #相当于调accept了
callback(key.fileobj,mask) #key.fileobj=文件句柄
3.2、selectors用法
①定义一个对象
sel = selectors.DefaultSelector()
②注册一个事件
说明:注册事件,只要来一个连接就调accept这个函数,就相当于之前select的用法,sel.register(server,selectors.EVENT_READ,accept) == inputs=[server,],readable,writeable,exceptional = select.select(inputs,outputs,inputs)意思是一样的。
sel.register(server,selectors.EVENT_READ,accept)
③循环事件
说明,这边这个events输出的是什么呐?
while True:
events = sel.select() #这个select,看起来是select,有可能调用的是epoll,看你操作系统是Windows的还是Linux的
print("事件:",events)
for key,mask in events:
callback = key.data #相当于调accept了
callback(key.fileobj,mask) #key.fileobj=文件句柄
打印events的结果:
事件: [(SelectorKey(fileobj=<socket.socket fd=276, family=AddressFamily.AF_INET,
type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>, fd=276, events=1,
data=<function accept at 0x000002722BB1D510>), 1)]
accepted <socket.socket fd=352, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM,
proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 63829)> from ('127.0.0.1', 63829)
事件: [(SelectorKey(fileobj=<socket.socket fd=352, family=AddressFamily.AF_INET,
type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1',
63829)>, fd=352, events=1, data=<function read at 0x000002722BB1D840>), 1)]
echoing b'ls' to <socket.socket fd=352, family=AddressFamily.AF_INET,
type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1',
63829)>
这样很容易明白了:
callback = key.data #第一次调用的是accept,第二次调用的是read
callback(key.fileobj,mask) #key.fileobj=文件句柄
四、高并发的客户端代码
4.1、客户端高并发代码
说明:这个在Windows上试的时候是select,最好在Linux上试,它默认支持的是epoll
import socket,sys
messages = [ b'This is the message. ',
b'It will be sent ',
b'in parts.',
]
server_address = ('localhost', 9999)
# Create a TCP/IP socket
socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(100)]
# Connect the socket to the port where the server is listening
print('connecting to %s port %s' % server_address)
for s in socks:
s.connect(server_address)
for message in messages:
# Send messages on both sockets
for s in socks:
print('%s: sending "%s"' % (s.getsockname(), message) )
s.send(message)
# Read responses on both sockets
for s in socks:
data = s.recv(1024)
print( '%s: received "%s"' % (s.getsockname(), data) )
if not data:
print(sys.stderr, 'closing socket', s.getsockname() )
想了解更多关于epoll的东西:请猛击这里