python 异步 select pooll epoll

概念:

首先列一下,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()时便得到通知。

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

 

 

 

这里玩一下select和poll的实例,不过这个例子对于理解异步和解决问题的办法都会有很大的帮助。一个串行的方法怎么可能实现并行的效果那?

 

  1 #!/usr/bin/env python
  2 # -*- coding: utf-8 -*-
  3 
  4 import select
  5 import socket
  6 import sys
  7 import Queue
  8 
  9 
 10 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 11 #socket本身是串行处理数据的,当A连接的时候B是无法进行"连接"的,A的会话完毕才可以进行B的连接。如果加上setblocking(0) B用户可以连接,但是也必须等待A处理完事才能处理B
 12 #的事情。
 13 server.setblocking(0)
 14 server_address=('10.64.8.92',10000)
 15 print >> sys.stderr,"starting up !!!!!"
 16 server.bind(server_address)
 17 
 18 #A连接后还可以在连接4个人,在多的人就无法连接。一共5个人可以排队
 19 server.listen(5)
 20 
 21 
 22 '''
 23 select 有三个表(自定义),input接受消息,output 回消息,error 错误信息
 24 select,回循环遍历每一个列表。不停的遍历默认1024文件描述符。不管有没有对象真的占用描述符都会一直循环,并且一个对象都没有的时候
 25 将进入阻塞状态
 26 '''
 27 #这里必须这样写把server放进去,新的连接信息其实并不会加入这个列表中去。这个列表始终只有server
 28 inputs=[server]
 29 
 30 #这个链列表是存储要发消息的连接对象
 31 outputs=[]
 32 
 33 #这个队列是存放每个对象发过来的消息
 34 message_queues={}
 35 
 36 #开始循环监听
 37 while inputs:
 38     print >>sys.stderr, '\nwaiting for the next event'
 39     #inputs列表只会有server,个人理解select.select就是监控server 文件描述符是否有变化
 40     readable, writable, exceptional = select.select(inputs, outputs, inputs)
 41 
 42     #开始循环readable 只要有链接对象过来那么这里就开始执行。
 43     for s in readable:
 44         #注意这里只有新创建的时候才为true,当连接之后在发消息过来这里为false(如果中途断开重新连接那算新的连接)
 45         if s is server:
 46             print "s:",s
 47             print "server:",server
 48             print "readable:",readable
 49             print "ser filen:",server.fileno()
 50             print "s filen:",s.fileno() 
 51             # A "readable" server socket is ready to accept a connection
 52             #connection这里我理解就是socket已经分配了一个文件描述符
 53             connection, client_address = s.accept()
 54             print >>sys.stderr, 'new connection from', client_address
 55             #这里的意思可能是不阻塞其它对象连接这个server,把这个注释掉也没有阻塞新客户端连接
 56             connection.setblocking(0)
 57             #将套接字对象放到inputs列表中,inputs过程就完事了
 58             inputs.append(connection)
 59 
 60             # Give the connection a queue for data we want to send
 61             #将套接字注册一个队列,存储消息用。就是通过这个队列的方法(先进先出)将消息发给接受的对象
 62             message_queues[connection] = Queue.Queue()
 63         else:#这个地方就是套接字对象已经在inpus列表中存在了
 64              data = s.recv(1024)
 65              if data:
 66                  # A readable client socket has data
 67                  print >>sys.stderr, 'received "%s" from %s' % (data, s.getpeername())
 68                  #将消息放到对应的套接字队列里面
 69                  message_queues[s].put(data)
 70                  # Add output channel for response 将套接如果不在outputs队列中那么就追加进去
 71                  if s not in outputs:
 72                      outputs.append(s)
 73              else:
 74                         # Interpret empty result as closed connection
 75                         print >>sys.stderr, 'closing', client_address, 'after reading no data'
 76                         # Stop listening for input on the connection
 77                         if s in outputs:
 78                             outputs.remove(s)  #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉
 79                         inputs.remove(s)    #inputs中也删除掉
 80                         s.close()           #把这个连接关闭掉
 81 
 82                         # Remove message queue
 83                         del message_queues[s]
 84 
 85     #这里循环writable,就是处理output队列
 86     for s in writable:
 87         try:
 88             #取套接字对象下的队列消息
 89             next_msg = message_queues[s].get_nowait()
 90         except Queue.Empty:
 91             # No messages waiting so stop checking for writability.
 92             print >>sys.stderr, 'output queue for', s.getpeername(), 'is empty'
 93             outputs.remove(s)
 94         else:#当消息没有报错那就说明队列中有消息,这时把消息发送出去,这个消息发给谁s中有记录所以不怕找不到人
 95             print >>sys.stderr, 'sending "%s" to %s' % (next_msg, s.getpeername())
 96             s.send(next_msg)
 97     #这里循环出错列表,将出错的套接字在三个列表和队列中全部清除掉
 98     for s in exceptional:
 99         print >>sys.stderr, 'handling exceptional condition for', s.getpeername()
100         # Stop listening for input on the connection
101         inputs.remove(s)
102         if s in outputs:
103             outputs.remove(s)
104         s.close()
105 
106         # Remove message queue
107         del message_queues[s]

 

 

 

 

 

poll的方法 跟select差不多只不过列表不用大家自己创建了

poll flags 状态,其实这个就相当于select自己建立的列表。poll就是不断的改变flags 状态然后做出相应的动作

 

 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 import select
 4 import socket
 5 import sys
 6 import Queue
 7 
 8 # Create a TCP/IP socket
 9 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
10 server.setblocking(0)
11 
12 # Bind the socket to the port
13 server_address = ('10.64.8.92', 10000)
14 print >>sys.stderr, 'starting up on %s port %s' % server_address
15 server.bind(server_address)
16 
17 # Listen for incoming connections
18 server.listen(5)
19 
20 # Keep up with the queues of outgoing messages
21 message_queues = {}
22 
23 
24 
25 TIMEOUT = 1000.
26 # Commonly used flag setes
27 READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR
28 READ_WRITE = READ_ONLY | select.POLLOUT
29 
30 
31 poller = select.poll()
32 #注册下服务器,当收到新连接的时候出发事件
33 poller.register(server, READ_ONLY)
34 
35 # 返回文件描述符和对象的一个映射关系
36 fd_to_socket = { server.fileno(): server,
37                }
38 while True:
39 
40     # Wait for at least one of the sockets to be ready for processing
41     print >>sys.stderr, '\nwaiting for the next event'
42     events = poller.poll(TIMEOUT)
43 
44     for fd, flag in events:
45 
46         # 检测套接字文件描述符
47         s = fd_to_socket[fd]
48         if flag & (select.POLLIN | select.POLLPRI):#新连接和消息接受在这里处理
49             if s is server:
50                 # "readable" 开始接受新的连接
51                 connection, client_address = s.accept()
52                 print >>sys.stderr, 'new connection from', client_address
53                 connection.setblocking(0)
54                 fd_to_socket[ connection.fileno() ] = connection
55                 poller.register(connection, READ_ONLY)
56                 # Give the connection a queue for data we want to send
57                 message_queues[connection] = Queue.Queue()
58             else:#如果不是新的连接这里传递过来的一定是数据。这里就接受一下
59                 data = s.recv(1024)
60                 if data:#
61                     # A readable client socket has data
62                     print >>sys.stderr, 'received "%s" from %s' % (data, s.getpeername())
63                     message_queues[s].put(data)
64                     # Add output channel for response,,修改状态  这个就是select中的output列表
65                     poller.modify(s, READ_WRITE)
66                 else:
67                     # Interpret empty result as closed connection
68                     print >>sys.stderr, 'closing', client_address, 'after reading no data'
69                     # Stop listening for input on the connection
70                     poller.unregister(s)
71                     s.close()
72 
73                     # Remove message queue
74                     del message_queues[s]
75         elif flag & select.POLLHUP: #客户端挂了或者客户端关闭了
76             # Client hung up
77             print >>sys.stderr, 'closing', client_address, 'after receiving HUP'
78             # Stop listening for input on the connection
79             poller.unregister(s)
80             s.close()
81         elif flag & select.POLLOUT:#回复数据
82             # Socket is ready to send data, if there is any to send.
83             try:
84                 next_msg = message_queues[s].get_nowait()
85             except Queue.Empty:
86                 # No messages waiting so stop checking for writability.
87                 print >>sys.stderr, 'output queue for', s.getpeername(), 'is empty'
88                 poller.modify(s, READ_ONLY)
89             else:
90                 print >>sys.stderr, 'sending "%s" to %s' % (next_msg, s.getpeername())
91                 s.send(next_msg)
92         elif flag & select.POLLERR:
93             print >>sys.stderr, 'handling exceptional condition for', s.getpeername()
94             # Stop listening for input on the connection
95             poller.unregister(s)
96             s.close()
97 
98             # Remove message queue
99             del message_queues[s]

 

 客户端连接代码:这个手动测试的代码方便理解异步中的每个步骤

 1 #!/usr/bin/env python
 2 import socket
 3 import sys
 4  
 5 HOST, PORT = "10.64.8.92",10000
 6 
 7  
 8 # Create a socket (SOCK_STREAM means a TCP socket)
 9 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
10 sock.connect((HOST, PORT))
11 while 1:
12         # Connect to server and send data
13         data=raw_input("echo server:")
14         sock.sendall(bytes(data))
15 
16         # Receive data from the server and shut down
17         received = str(sock.recv(1024))
18         print received

 原文:

https://pymotw.com/2/select/  

posted @ 2016-09-01 16:55  MKY-门可意  阅读(1001)  评论(0编辑  收藏  举报