首先列一下,sellect、poll、epoll三者的区别
select
select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
poll
poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。
epoll
直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。
1 2 3 4 5 6 |
|
nginx:epoll
利用select监听终端操作:
#!/usr/bin/env python #encoding: utf-8 #需linux环境 import select import threading import sys while True: readable,writeable,error = select.select([sys.stdin,],[],[],1) if sys.stdin in readable: print ‘select get stdin‘,sys.stdin.readline()
利用select实现伪同时处理多个Socket客户端请求:服务端:
1、通过try异常处理实现
2、通过select实现。并且可以同时监听多个ip端口
#!/usr/bin/env python #encoding: utf-8 import socket import select ip_port = (‘127.0.0.1‘,8888) sk = socket.socket() sk.bind(ip_port) sk.listen(5) sk.setblocking(False) #是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。 ip_port2 = (‘127.0.0.1‘,9999) sk2 = socket.socket() sk2.bind(ip_port2) sk2.listen(5) sk2.setblocking(False) while True: rlist,w,e = select.select([sk,sk2],[],[],1) for r in rlist: conn,address = r.accept() print address
利用select实现伪同时处理多个Socket客户端请求:服务端
import socket import select ip_port = (‘127.0.0.1‘,8888) sk = socket.socket() sk.bind(ip_port) sk.listen(5) sk.setblocking(False) inputs = [sk,] while True: rlist,w,e = select.select(inputs,[],[],1) print ‘input:‘,inputs print ‘resut:‘,rlist for r in rlist: #当客户端第一次连接服务器时 if r == sk: conn,address = r.accept() print address inputs.append(conn) #当客户端连上服务器之后,再次发送数据时 else: client_data = r.recv(1024) if client_data: r.sendall(client_data) else: #如果client端断开连接,则移除inputs中的r inputs.remove(r) sk.close()
利用select实现伪同时处理多个Socket客户端请求:客户端
import socket ip_port = (‘127.0.0.1‘,8888) sk = socket.socket() sk.connect(ip_port) sk.settimeout(5) while True: inp = raw_input(‘please input:‘) sk.sendall(inp) print sk.recv(1024) sk.close()
select实现socket服务端(接收和返回数据)
import socket import select ip_port = (‘127.0.0.1‘,8888) sk = socket.socket() sk.bind(ip_port) sk.listen(5) sk.setblocking(False) inputs = [sk,] outputs = [] while True: rlist,wlist,e = select.select(inputs,outputs,inputs,1) for r in rlist: if r == sk: conn,address = r.accept() inputs.append(conn) else: client_data = r.recv(1024) if client_data: outputs.append(r) for w in wlist: w.sendall(‘123‘) outputs.remove(w) sk.close()
select实现socket服务端六(分离读写操作实现)
###########server端##################
import socket import select import Queue ip_port = (‘127.0.0.1‘,8888) server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建一个socket server.bind(ip_port) server.listen(5) server.setblocking(0) #非阻塞 inputs = [ server ] outputs = [] messages = {} while inputs: rlist,wlist,elist = select.select(inputs,outputs,inputs) #rlist读,wlist写,elist错误 for r in rlist: if r is server: conn,address = r.accept() conn.setblocking(0) inputs.append(conn) messages[conn] = Queue.Queue() else: client_data = r.recv(1024) if client_data: messages[r].put(client_data) if r not in outputs: outputs.append(r) else: if r in outputs: outputs.remove(r) inputs.remove(r) r.close() for w in wlist: try: data = messages[w].get_nowait() except Queue.Empty: outputs.remove(w) else: w.send(data) for e in elist: inputs.remove(e) if e in outputs: outputs.remove(e) e.close() del messages[e]
###########client端##################
import socket import sys #同时要发送的内容 messages = [ ‘This is the message. ‘, ‘It will be sent ‘, ‘in parts.‘, ] server_address = (‘127.0.0.1‘, 8888) #同时建立2个socket,实现并发 socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM), socket.socket(socket.AF_INET, socket.SOCK_STREAM), ] print >>sys.stderr, ‘connecting to %s port %s‘ % server_address for s in socks: s.connect(server_address) for message in messages: for s in socks: print >>sys.stderr, ‘%s: sending "%s"‘ % (s.getsockname(), message) s.send(message) for s in socks: data = s.recv(1024) print >>sys.stderr, ‘%s: received "%s"‘ % (s.getsockname(), data) if not data: print >>sys.stderr, ‘closing socket‘, s.getsockname() s.close()