select用于实现多路复用,就是在不多开线程或者进程的情况,可以实现多个socket连接
select依赖系统支持,当系统内核检测到有进程指定的IO有输入或者输出时,比如检测到有socket连接过来就会通知该进程,从而可以实现一个进程同时管理多个socket连接
select技术与多进程、多线程区别:
select技术与多进程或者多线程相比,系统开销要低,打个比方:
如果去食堂吃饭,我们通常都需要排队,多进程/多线程就是来一个人开一个窗口,如果打完了饭,没有关闭窗口或者就站在窗口那吃饭,窗口资源就浪费了,select技术在窗口排队,分成付费窗口与取饭窗口及充值窗口,然后有个前台师妹检查大家的情况安排排队
select、poll、epoll之间区别:
- select需要人排队,队伍长度有限制
- poll需要人排队,通过优化队伍形状使得队伍长度可以更长
- epoll先领号,等待叫号领饭,接近无限排队了
导入select模块
import select
select.select方法
查询三组文件描述符代表的对象的输入、输出与异常状态,该方法在windows系统下只支持socket文件描述符,通过轮询,返回待输入、待输出、等待异常的三组对象。
rlist, wlist, xlist = select.select(rlist, wlist, xlist[, timeout])
参数名 | 说明 |
rlist | 一组等待输入的对象的整数文件描述符组成的列表,可以为空 |
wlist | 一组等待输出的对象的整数文件描述符组成的列表, 可以为空 |
xlist | 一组等待异常的对象的整数文件描述符组成的列表, 可以为空 |
timeout | 一个浮点数以秒为单位的超时时间,如果忽略该参数就直到有可用的对象为止,如果为0,将只轮询一次,并立即返回 |
多线程与select对比
select实现的多路复用服务端
from socket import *
from select import *
from time import sleep
address = ("127.0.0.1", 5000)
s = socket(AF_INET, SOCK_STREAM)
s.bind(address)
s.listen(3)
buffersize = 1024
r_sockets = [s]
w_sockets = []
def reply(client_s):
data = client_s.recv(buffersize)
print("来自客户端的数据",data)
print("Client:",client_s)
w_sockets.append(client_s)
try:
while True:
readable, writetable, exceptional = select(r_sockets, w_sockets, [])
for r in readable:
if r is s:
c_sock, c_addr = s.accept()
r_sockets.append(c_sock)
else:
reply(r)
for w in writetable:
print(w)
w.send(b"ok"+str(w).encode())
w_sockets.remove(w)
finally:
s.close()
多线程实现的支持多客户端的服务端
from socket import *
from threading import Thread
from time import sleep
address = ("127.0.0.1", 5000)
s = socket(AF_INET, SOCK_STREAM)
s.bind(address)
s.listen(3)
buffersize = 1024
sockets = [s]
def reply(client_s):
while True:
data = client_s.recv(buffersize)
print("来自客户端的数据",data)
print("Client:",client_s)
if data:
client_s.send(b"ok"+data)
try:
while True:
client_s, client_addr = s.accept()
t = Thread(target=reply, args=(client_s,))
t.start()
finally:
s.close()
POLL实现
创建poll对象
from select import poll
p = poll()
poll对象方法
方法 | 说明 |
register (fd[, eventmask]) |
注册新的需要轮询的对象 fd - 文件描述符或者拥有fileno()方法的对象 eventmask - 需要轮询事件的掩码 |
poll() | 对注册的对象进行轮询,并返回一个由(fd, eventmask)组成的元组列表 |
modify (fd, eventmask) |
修改注册对象的轮询事件 |
unregister (fd) |
删除注册事件 |
事件掩码eventmask
事件掩码是select模块的常量
事件掩码 | 说明 |
select.POLLIN | 可用于读取数据 |
select.POLLPRI | 可用于读取的紧急数据 |
select.POLLOUT | 准备写入 |
select.POLLERR | 错误情况 |
select.POLLHUP | 保持状态 |
select.POLLNVAL | 无效请求 |
poll服务端实现示例
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from socket import *
from select import *
from time import sleep
import select
address = ("127.0.0.1", 5000)
s = socket(AF_INET, SOCK_STREAM)
s.bind(address)
s.listen(3)
buffersize = 1024
p = poll()
p.register(s, select.POLLIN)
fd_sockets = {s.fileno():s}
def reply(client_s):
print("Client:",client_s)
data = client_s.recv(buffersize)
print("来自客户端的数据",data)
p.register(client_s, select.POLLOUT)
try:
while True:
sockets = p.poll()
for fd,event in sockets:
_socket = fd_sockets[fd]
if select.POLLIN & event:
if fd == s.fileno():
c_sock, c_addr = s.accept()
print("来自", c_addr,"的连接...")
fd_sockets[c_sock.fileno()] = c_sock
p.register(c_sock, select.POLLIN)
else:
reply(_socket)
if select.POLLOUT & event:
print(_socket)
_socket.send(b"ok"+str(_socket).encode()+b"\n")
p.modify(_socket, select.POLLIN)
finally:
s.close()
epoll对象
epoll需要Linux 2.5.44以上版本支持。使用方法与poll相似。
创建epoll对象
from select import epoll
ep =epoll()
epoll对象方法
方法 | 说明 |
register (fd[, eventmask]) |
注册新的需要轮询的对象 fd - 文件描述符或者拥有fileno()方法的对象 eventmask - 需要轮询事件的掩码 |
poll() | 对注册的对象进行轮询,并返回一个由(fd, eventmask)组成的元组列表 |
modify (fd, eventmask) |
修改注册对象的轮询事件 |
unregister (fd) |
删除注册事件 |
fromfd(fd) | 从给定的fd文件描述符创建epoll对象 |
事件掩码eventmask
常量 | 说明 |
---|---|
EPOLLIN |
可供阅读 |
EPOLLOUT |
可写 |
EPOLLPRI |
紧急数据供阅读 |
EPOLLERR |
错误 |
EPOLLHUP |
挂断 |
EPOLLET |
设置边沿触发行为,默认为“级别触发”行为 |
EPOLLONESHOT |
设置一次性行为。触发一个事件后,fd在内部被禁用 |
EPOLLEXCLUSIVE |
当关联的fd有事件时,只唤醒一个epoll对象。默认(如果未设置此标志)是唤醒在fd上轮询的所有epoll对象。 |
EPOLLRDHUP |
流套接字对等关闭连接或关闭写入一半的连接。 |
EPOLLRDNORM |
相当于 EPOLLIN |
EPOLLRDBAND |
可以读取优先数据带。 |
EPOLLWRNORM |
相当于 EPOLLOUT |
EPOLLWRBAND |
可以写入优先级数据。 |
EPOLLMSG |
忽略。 |
讨论区