简介
Python中的select模块专注于I/O多路复用,提供了select poll epoll三个方法(其中后两个在Linux中可用,windows仅支持select),另外也提供了kqueue方法(freeBSD系统)
博文说明:
1,本文中的代码都已运行成功;
2,所有截图都是博主自己截取的,写一篇完整的博客确实很辛苦,整理素材输出文档;
3,如果转载,请注明出处。
运行环境说明:
OS发行版:CentOS7.4
python版本:
【epoll实例服务端1】使用python3.6和python2.7.5
【epoll实例服务端2】使用python2.7.5
【所有客户端】使用python2.7.5
select方法
进程指定内核监听哪些文件描述符(最多监听1024个fd)的哪些事件,当没有文件描述符事件发生时,进程被阻塞;当一个或者多个文件描述符事件发生时,进程被唤醒。
当我们调用select()时:
- 上下文切换转换为内核态
- 将fd从用户空间复制到内核空间
- 内核遍历所有fd,查看其对应事件是否发生
- 如果没发生,将进程阻塞,当设备驱动产生中断或者timeout时间后,将进程唤醒,再次进行遍历
- 返回遍历后的fd
- 将fd从内核空间复制到用户空间
fd:file descriptor 文件描述符
fd_r_list, fd_w_list, fd_e_list = select.select(rlist, wlist, xlist, [timeout])
参数: 可接受四个参数(前三个必须)
- rlist: wait until ready for reading
- wlist: wait until ready for writing
- xlist: wait for an “exceptional condition”
- timeout: 超时时间
返回值:三个列表
select方法用来监视文件描述符(当文件描述符条件不满足时,select会阻塞),当某个文件描述符状态改变后,会返回三个列表
- 当参数1 序列中的fd满足“可读”条件时,则获取发生变化的fd并添加到fd_r_list中
- 当参数2 序列中含有fd时,则将该序列中所有的fd添加到 fd_w_list中
- 当参数3 序列中的fd发生错误时,则将该发生错误的fd添加到 fd_e_list中
- 当超时时间为空,则select会一直阻塞,直到监听的句柄发生变化,当超时时间 = n(正整数)时,那么如果监听的句柄均无任何变化,则select会阻塞n秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
select实例:利用select实现一个可并发的服务端
服务端:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import socket
import select
ip_port = ("192.168.12.172", 9999)
sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk1.bind(ip_port)
sk1.listen(5)
sk1.setblocking(0)
inputs = [sk1,]
print "inputs: ", inputs
while True:
#要不断的调用select函数来检查给定的类文件对象是否有数据就绪,当且仅当有新客户端连接进来的时候,sk1服务端这个套接字对象才是读就绪的,也就是才会存在于readable_list列表中;
readable_list, writeable_list, error_list = select.select(inputs, [], inputs, 1)
print "readable_list: ", readable_list
for r in readable_list:
#当客户端第一次连接服务端时,sk1服务端套接字对象,通过调用accept方法获取到新进来的客户端套接字,然后把新进来的客户端套接字对象追加到,待select检测的inputs列表中。
if sk1 == r:
print "r=sk1: ", r
print "first accept"
request, address = r.accept()
request.setblocking(0)
inputs.append(request)
print "first inputs: ", inputs
#当客户端连接上服务器端之后,再次发送数据时
else:
print "myr: ", r
received = r.recv(1024)
print "received: ", received
#当正常接收客户端发送的数据时
if received:
print "received data: ", received
#当客户端关闭程序时
else:
inputs.remove(r)
sk1.close()
客户端:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import socket
ip_port = ("192.168.12.172", 9999)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(ip_port)
while True:
inp = raw_input("Please enter:")
client.sendall(inp)
client.close()
在服务端我们可以看到,我们需要不停的调用select, 这就意味着:
- 当文件描述符过多时,文件描述符在用户空间与内核空间进行copy会很费时
- 当文件描述符过多时,内核对文件描述符的遍历也很浪费时间
- select最大仅仅支持1024个文件描述符
poll与select相差不大,本文不作介绍