请稍候,加载中....

select模块

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(fdeventmask) 修改注册对象的轮询事件
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(fdeventmask) 修改注册对象的轮询事件
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 忽略。

Python学习手册-