python poll_python select epoll poll的解析

本文介绍了Python中用于多路I/O就绪通知的select、poll和epoll的区别及其使用方法。select是最早出现的,适用于所有平台,但存在文件描述符数量限制。poll与select类似,但没有数量限制,且使用事件触发。epoll是Linux 2.6引入的高性能I/O接口,支持边缘触发和水平触发,避免了文件描述符拷贝的开销。文章通过代码示例展示了这三种方法在处理并发连接时的工作原理。
摘要由CSDN通过智能技术生成

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()方法接收并监控3个通信列表, 第一个是所有的输入的data,就是指外部发过来的数据,第2个是监控和接收所有要发出去的data(outgoing data),第3个监控错误信息,接下来我们需要创建2个列表来包含输入和输出信息来传给select().

# Sockets from which we expect to read

inputs = [ server ]

# Sockets to which we expect to write

outputs = [ ]

所有客户端的进来的连接和数据将会被server的主循环程序放在上面的list中处理,我们现在的server端需要等待连接可写(writable)之后才能过来,然后接收数据并返回(因此不是在接收到数据之后就立刻返回),因为每个连接要把输入或输出的数据先缓存到queue里,然后再由select取出来再发出去。

# Outgoing message queues (socket:Queue)

message_queues = {}

The main portion of the server program loops, calling select() to block and wait for network activity.

下面是此程序的主循环,调用select()时会阻塞和等待直到新的连接和数据进来

while inputs:

# Wait for at least one of the sockets to be ready for processing

print >>sys.stderr, '\nwaiting for the next event'

readable, writable, exceptional = select.select(inputs, outputs, inputs)

当你把inputs,outputs,exceptional(这里跟inputs共用)传给

Readable list 中的socket 可以有3种可能状态,第一种是如果这个socket是main "server" socket,它负责监听客户端的连接,如果这个main server socket出现在readable里,那代表这是server端已经ready来接收一个新的连接进来了,为了让这个main server能同时处理多个连接,在下面的代码里,我们把这个main server的socket设置为非阻塞模式。

第二种情况是这个socket是已经建立了的连接,它把数据发了过来,这个时候你就可以通过recv()来接收它发过来的数据,然后把接收到的数据放到queue里,这样你就可以把接收到的数据再传回给客户端了。

第三种情况就是这个客户端已经断开了,所以你再通过recv()接收到的数据就为空了,所以这个时候你就可以把这个跟客户端的连接关闭了。

对于writable list中的socket,也有几种状态,如果这个客户端连接在跟它对应的queue里有数据,就把这个数据取出来再发回给这个客户端,否则就把这个连接从output list中移除,这样下一次循环select()调用时检测到outputs list中没有这个连接,那就会认为这个连接还处于非活动状态

最后,如果在跟某个socket连接通信过程中出了错误,就把这个连接对象在inputs\outputs\message_queue中都删除,再把连接关闭掉

1 #coding:UTF8

2

3 importsocket4 importsys5

6 messages = [ 'This is the message.',7 'It will be sent',8 'in parts.',9 ]10 server_address = ('localhost', 10003)11

12 #Create a TCP/IP socket

13 socks =[ socket.socket(socket.AF_INET, socket.SOCK_STREAM),14 socket.socket(socket.AF_INET, socket.SOCK_STREAM),15 ]16

17 #Connect the socket to the port where the server is listening

18 print >>sys.stderr, 'connecting to %s port %s' %server_address19 for s insocks:20 s.connect(server_address)21

22 for message inmessages:23

24 #Send messages on both sockets

25 for s insocks:26 print >>sys.stderr, '%s: sending "%s"' %(s.getsockname(), message)27 s.send(message)28

29 #Read responses on both sockets

30 for s insocks:31 data = s.recv(1024)32 print >>sys.stderr, '%s: received "%s"' %(s.getsockname(), data)33 if notdata:34 print >>sys.stderr, 'closing socket', s.getsockname()

client

客户端程序展示了如何通过select()对socket进行管理并与多个连接同时进行交互,通过循环通过每个socket连接给server发送和接收数据。

server:

starting up on localhost prot10000waitingforthe next event

new connectionfrom ('127.0.0.1', 54812)

waitingforthe next event

new connectionfrom ('127.0.0.1', 54813)

received"This is the message." from ('127.0.0.1', 54812)

waitingforthe next event

received"This is the message." from ('127.0.0.1', 54813)

sending"This is the message." to ('127.0.0.1', 54812)

waitingforthe next event

output queuefor ('127.0.0.1', 54812) isempty

sending"This is the message." to ('127.0.0.1', 54813)

waitingforthe next event

output queuefor ('127.0.0.1', 54813) isempty

waitingforthe next event

received"It will be sent" from ('127.0.0.1', 54812)

received"It will be sent" from ('127.0.0.1', 54813)

waitingforthe next event

sending"It will be sent" to ('127.0.0.1', 54812)

sending"It will be sent" to ('127.0.0.1', 54813)

waitingforthe next event

output queuefor ('127.0.0.1', 54812) isempty

output queuefor ('127.0.0.1', 54813) isempty

waitingforthe next event

received"in parts." from ('127.0.0.1', 54812)

received"in parts." from ('127.0.0.1', 54813)

waitingforthe next event

sending"in parts." to ('127.0.0.1', 54812)

sending"in parts." to ('127.0.0.1', 54813)

waitingforthe next event

output queuefor ('127.0.0.1', 54812) isempty

output queuefor ('127.0.0.1', 54813) isempty

waitingforthe next event

closing ('127.0.0.1', 54813) after reading no data

closing ('127.0.0.1', 54813) after reading no data

waitingforthe next event

client:

connecting to localhost port10000('127.0.0.1', 54812): sending "This is the message."('127.0.0.1', 54813): sending "This is the message."('127.0.0.1', 54812): received "THIS IS THE MESSAGE."('127.0.0.1', 54813): received "THIS IS THE MESSAGE."('127.0.0.1', 54812): sending "It will be sent"('127.0.0.1', 54813): sending "It will be sent"('127.0.0.1', 54812): received "IT WILL BE SENT"('127.0.0.1', 54813): received "IT WILL BE SENT"('127.0.0.1', 54812): sending "in parts."('127.0.0.1', 54813): sending "in parts."('127.0.0.1', 54812): received "IN PARTS."('127.0.0.1', 54813): received "IN PARTS."

运行结果

原文:http://pymotw.com/2/select/

poll

poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。

poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll() 的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。

在Python中调用poll

select.poll(),返回一个poll的对象,支持注册和注销文件描述符。

poll.register(fd[, eventmask])注册一个文件描述符,注册后,可以通过poll()方法来检查是否有对应的I/O事件发生。fd可以是i 个整数,或者有返回整数的fileno()方法对象。如果File对象实现了fileno(),也可以当作参数使用。

eventmask是一个你想去检查的事件类型,它可以是常量POLLIN, POLLPRI和 POLLOUT的组合。如果缺省,默认会去检查所有的3种事件类型。

事件常量意义

POLLIN

有数据读取

POLLPRT

有数据紧急读取

POLLOUT

准备输出:输出不会阻塞

POLLERR

某些错误情况出现

POLLHUP

挂起

POLLNVAL

无效请求:描述无法打开

poll.modify(fd, eventmask) 修改一个已经存在的fd,和poll.register(fd, eventmask)有相同的作用。如果去尝试修改一个未经注册的fd,会引起一个errno为ENOENT的IOError。

poll.unregister(fd)从poll对象中注销一个fd。尝试去注销一个未经注册的fd,会引起KeyError。

poll.poll([timeout])去检测已经注册了的文件描述符。会返回一个可能为空的list,list中包含着(fd, event)这样的二元组。 fd是文件描述符, event是文件描述符对应的事件。如果返回的是一个空的list,则说明超时了且没有文件描述符有事件发生。timeout的单位是milliseconds,如果设置了timeout,系统将会等待对应的时间。如果timeout缺省或者是None,这个方法将会阻塞直到对应的poll对象有一个事件发生。

#coding: utf-8

importselect, socket

response= b"hello world"serversocket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)

serversocket.bind(('localhost', 10000))

serversocket.listen(1)

serversocket.setblocking(0)#poll =select.poll()

poll.register(serversocket.fileno(), select.POLLIN)

connections={}whileTrue:for fd, event inpoll.poll():if event ==select.POLLIN:if fd ==serversocket.fileno():

con, addr=serversocket.accept()

poll.register(con.fileno(), select.POLLIN)

connections[con.fileno()]=conelse:

con=connections[fd]

data= con.recv(1024)ifdata:

poll.modify(con.fileno(), select.POLLOUT)elif event ==select.POLLOUT:

con=connections[fd]

con.send(response)

poll.unregister(con.fileno())

con.close()

View Code

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()时便得到通知。

在Python中调用epoll

select.epoll([sizehint=-1])返回一个epoll对象。

eventmask

事件常量意义

EPOLLIN

读就绪

EPOLLOUT

写就绪

EPOLLPRI

有数据紧急读取

EPOLLERR

assoc. fd有错误情况发生

EPOLLHUP

assoc. fd发生挂起

EPOLLRT

设置边缘触发(ET)(默认的是水平触发)

EPOLLONESHOT

设置为 one-short 行为,一个事件(event)被拉出后,对应的fd在内部被禁用

EPOLLRDNORM

和 EPOLLIN 相等

EPOLLRDBAND

优先读取的数据带(data band)

EPOLLWRNORM

和 EPOLLOUT 相等

EPOLLWRBAND

优先写的数据带(data band)

EPOLLMSG

忽视

epoll.close()关闭epoll对象的文件描述符。

epoll.fileno返回control fd的文件描述符number。

epoll.fromfd(fd)用给予的fd来创建一个epoll对象。

epoll.register(fd[, eventmask])在epoll对象中注册一个文件描述符。(如果文件描述符已经存在,将会引起一个IOError)

epoll.modify(fd, eventmask)修改一个已经注册的文件描述符。

epoll.unregister(fd)注销一个文件描述符。

epoll.poll(timeout=-1[, maxevnets=-1])等待事件,timeout(float)的单位是秒(second)。

1 #coding:Utf8

2 importsocket, select3

4 EOL1 = b'\n\n'

5 EOL2 = b'\n\r\n'

6 response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'

7 response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'

8 response += b'Hello, world!'

9

10 serversocket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)11 serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)12 serversocket.bind(('localhost', 10000))13 serversocket.listen(1)14 serversocket.setblocking(0)15

16 epoll =select.epoll()17 epoll.register(serversocket.fileno(), select.EPOLLIN)18

19 try:20 connections = {}; requests = {}; responses ={}21 whileTrue:22 events = epoll.poll(1)23 for fileno, event inevents:24 if fileno ==serversocket.fileno():25 connection, address =serversocket.accept()26 connection.setblocking(0)27 epoll.register(connection.fileno(), select.EPOLLIN)28 connections[connection.fileno()] =connection29 requests[connection.fileno()] = b''

30 responses[connection.fileno()] =response31 elif event &select.EPOLLIN:32 requests[fileno] += connections[fileno].recv(1024)33 if EOL1 in requests[fileno] or EOL2 inrequests[fileno]:34 epoll.modify(fileno, select.EPOLLOUT)35 print('-'*40 + '\n' + requests[fileno].decode()[:-2])36 elif event &select.EPOLLOUT:37 byteswritten =connections[fileno].send(responses[fileno])38 responses[fileno] =responses[fileno][byteswritten:]39 if len(responses[fileno]) ==0:40 epoll.modify(fileno, 0)41 connections[fileno].shutdown(socket.SHUT_RDWR)42 elif event &select.EPOLLHUP:43 epoll.unregister(fileno)44 connections[fileno].close()45 delconnections[fileno]46 finally:47 epoll.unregister(serversocket.fileno())48 epoll.close()49 serversocket.close()

View Code

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值