select、poll、epoll三者的区别
select
select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组(在linux中一切事物皆文件,块设备,socket连接等。),当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位(变成ready),使得进程可以获得这些文件描述符从而进行后续的读写操作(select会不断监视网络接口的某个目录下有多少文件描述符变成ready状态【在网络接口中,过来一个连接就会建立一个'文件'】,变成ready状态后,select就可以操作这个文件描述符了)。
【socketserver是通过多线程来处理多个请求,每个连接过来分配一个线程来处理,但是select是单进程的,一个进程执行代码肯定就是串行的,但是现在就要通过一个进程来实现并发的效果,一个进程下只有一个主线程,也就说说用一个线程实现并发的效果。为什么要用一个进程实现多并发而不采用多线程实现多并发呢?
==========答:因为一个进程实现多并发比多线程是实现多并发的效率还要高,因为启动多线程会有很多的开销,而且CPU要不断的检查每个线程的状态,确定哪个线程是否可以执行。这个对系统来说也是有压力的,用单进程的话就可以避免这种开销和给系统带来的压力,
那么单进程是如何实现多并发的呢???
========答:很巧妙的使用了生产者和消费者的模式(异步),生产者和消费者可以实现非阻塞,一个socketserver通过select接收多个连接过来(之前的socket一个进程只能接收一个连接,当接收新的连接的时候产生阻塞,因为这个socket进程要先和客户端进行通信,二者是彼此互相等待的【客户端发一条消息,服务端收到,客户端等着返回....服务端等着接收.........】一直在阻塞着,这个时候如果再来一个连接,要等之前的那个连接断了,这个才可以连进来。-----------也就是说用基本的socket实现多进程是阻塞的。为了解决这个问题采用每来一个连接产生一个线程,是不阻塞了,但是当线程数量过多的时候,对于cpu来说开销和压力是比较大的。)对于单个socket来说,阻塞的时候大部分的时候都是在等待IO操作(网络操作也属于IO操作)。为了避免这种情况,就出现了异步=============客户端发起一个连接,会在服务端注册一个文件句柄,服务端会不断轮询这些文件句柄的列表,主进程和客户端建立连接而没有启动线程,这个时候主进程和客户端进行交互,其他的客户端是无法连接主进程的,为了实现主进程既能和已连接的客户端收发消息,又能和新的客户端建立连接,就把轮询变的非常快(死循环)去刷客户端连接进来的文件句柄的列表,只要客户端发消息了,服务端读取了消息之后,有另一个列表去接收给客户端返回的消息,也不断的去刷这个列表,刷出来后返回给客户端,这样和客户端的这次通信就完成了,但是跟客户端的连接还没有断,但是就进入了下一次的轮询。】
select 优点
select目前几乎在所有的平台上支持,良好跨平台性。
select 缺点
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多的时候会很大
- 单个进程能够监视的fd数量存在最大限制,在linux上默认为1024(可以通过修改宏定义或者重新编译内核的方式提升这个限制)
- 并且由于select的fd是放在数组中,并且每次都要线性遍历整个数组,当fd很多的时候,开销也很大
python select
调用select的函数为readable,writable,exceptional = select.select(rlist, wlist, xlist[, timeout]),前三个参数都分别是三个列表,数组中的对象均为waitable object:均是整数的文件描述符(file descriptor)或者一个拥有返回文件描述符方法fileno()的对象;
-
rlist: 等待读就绪的list -
wlist: 等待写就绪的list -
errlist: 等待“异常”的list
select方法用来监视文件描述符,如果文件描述符发生变化,则获取该描述符。2、当 rlist 序列中的描述符发生可读时(accetp和read),则获取发生变化的描述符并添加到 readable 序列中
3、当 wlist 序列中含有描述符时,则将该序列中所有的描述符添加到 writable 序列中
4、当 errlist序列中的句柄发生错误时,则将该发生错误的句柄添加到 exceptional 序列中
5、当 超时时间 未设置,则select会一直阻塞,直到监听的描述符发生变化
当 超时时间 = 1 时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的描述符(fd)有变化,则直接执行。
file对象(比如sys.stdin,或者会被open()和os.open()返回的object),socket object将会返回socket.socket()。也可以自定义类,只要有一个合适的fileno()的方法(需要真实返回一个文件描述符,而不是一个随机的整数)。select 示例:
Python的select()方法直接调用操作系统的IO接口,它监控sockets,open files, and pipes(所有带fileno()方法的文件句柄)何时变成readable 和writeable, 或者通信错误,select()使得同时监控多个连接变的简单,并且这比写一个长循环来等待和监控多客户端连接要高效,因为select直接通过操作系统提供的C的网络接口进行操作,而不是通过Python的解释器
1 #coding:UTF8 2 3 import select 4 import socket 5 import sys 6 import Queue 7 8 #创建一个TCP/IP 进程 9 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 10 server.setblocking(0) 11 12 #连接地址和端口 13 server_address = ('localhost',10000) 14 print >>sys.stderr,'starting up on %s prot %s' % server_address 15 server.bind(server_address) 16 17 #最大允许链接数 18 server.listen(5) 19 20 inputs = [ server ] 21 outputs = [] 22 23 message_queues = {} 24 25 while inputs: 26 print >>sys.stderr,'\nwaiting for the next event' 27 readable,writable,exceptional = select.select(inputs,outputs,inputs) 28 29 # Handle inputs 30 for s in readable: 31 32 if s is server: 33 # A "readable" server socket is ready to accept a connection 34 connection, client_address = s.accept() 35 print >>sys.stderr, 'new connection from', client_address 36 #connection.setblocking(0) 37 inputs.append(connection) 38 39 # Give the connection a queue for data we want to send 40 message_queues[connection] = Queue.Queue() 41 42 else: 43 data = s.recv(1024) 44 if data: 45 # A readable client socket has data 46 print >>sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) 47 message_queues[s].put(data) #这个s相当于connection 48 # Add output channel for response 49 if s not in outputs: 50 outputs.append(s) 51 52 else: 53 # Interpret empty result as closed connection 54 print >>sys.stderr, 'closing', client_address, 'after reading no data' 55 # Stop listening for input on the connection 56 if s in outputs: 57 outputs.remove(s) #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉 58 inputs.remove(s) #inputs中也删除掉 59 s.close() #把这个连接关闭掉 60 61 # Remove message queue 62 del message_queues[s] 63 64 # Handle outputs 65 for s in writable: 66 try: 67 next_msg = message_queues[s].get_nowait() 68 except Queue.Empty: 69 # No messages waiting so stop checking for writability. 70 print >>sys.stderr, 'output queue for', s.getpeername(), 'is empty' 71 outputs.remove(s) 72 else: 73 print >>sys.stderr, 'sending "%s" to %s' % (next_msg, s.getpeername()) 74 s.send(next_msg.upper()) 75 # Handle "exceptional conditions" 76 for s in exceptional: 77 print >>sys.stderr, 'handling exceptional condition for', s.getpeername() 78 # Stop listening for input on the connection 79 inputs.remove(s) 80 if s in outputs: 81 outputs.remove(s) 82 s.close() 83 84 # Remove message queue 85 del message_queues[s]