select/poll/epoll的区别及其Python示例

版权声明:作者:人学物理死的早 出处:https://blog.csdn.net/weixin_39561473 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。 https://blog.csdn.net/weixin_39561473/article/details/86770854

1 select/poll/epoll的区别

  I/O多路复用的本质就是用select/poll/epoll,去监听多个socket对象,如果其中的socket对象有变化,只要有变化,用户进程就知道了。

  select是不断轮询去监听的socket,socket个数有限制,一般为1024个;

  poll还是采用轮询方式监听,只不过没有个数限制;

  epoll并不是采用轮询方式去监听了,而是当socket有变化时通过回调的方式主动告知用户进程。

2 Python select示例

  Python的select()方法直接调用操作系统的IO接口,它监控sockets,open files, and pipes(所有带fileno()方法的文件句柄)何时变成readable 和writeable, 或者通信错误,select()使得同时监控多个连接变的简单,并且这比写一个长循环来等待和监控多客户端连接要高效,因为select直接通过操作系统提供的C的网络接口进行操作,而不是通过Python的解释器。

  注意:Using Python’s file objects with select() works for Unix, but is not supported under Windows.

  接下来通过echo server例子要以了解select 是如何通过单进程实现同时处理多个非阻塞的socket连接的:

import select
import socket
import sys
import Queue
 
# Create a TCP/IP socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(0)
 
# Bind the socket to the port
server_address = ('localhost', 10000)
print >>sys.stderr, 'starting up on %s port %s' % server_address
server.bind(server_address)
 
# Listen for incoming connections
server.listen(5)

  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 = {}

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

1 while inputs:
2 
3     # Wait for at least one of the sockets to be ready for processing
4     print >>sys.stderr, '\nwaiting for the next event'
5     readable, writable, exceptional = select.select(inputs, outputs, inputs)

  当你把inputs,outputs,exceptional(这里跟inputs共用)传给select()后,它返回3个新的list,我们上面将他们分别赋值为readable,writable,exceptional, 所有在readable list中的socket连接代表有数据可接收(recv),所有在writable list中的存放着你可以对其进行发送(send)操作的socket连接,当连接通信出现error时会把error写到exceptional列表中。

扫描二维码关注公众号,回复: 5157028 查看本文章

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

 1 # Handle inputs
 2 for s in readable:
 3  
 4     if s is server:
 5         # A "readable" server socket is ready to accept a connection
 6         connection, client_address = s.accept()
 7         print >>sys.stderr, 'new connection from', client_address
 8         connection.setblocking(0)
 9         inputs.append(connection)
10  
11         # Give the connection a queue for data we want to send
12         message_queues[connection] = Queue.Queue()

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

1 else:
2      data = s.recv(1024)
3      if data:
4          # A readable client socket has data
5          print >>sys.stderr, 'received "%s" from %s' % (data, s.getpeername())
6          message_queues[s].put(data)
7          # Add output channel for response
8          if s not in outputs:
9              outputs.append(s)

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

 1 else:
 2     # Interpret empty result as closed connection
 3     print >>sys.stderr, 'closing', client_address, 'after reading no data'
 4     # Stop listening for input on the connection
 5     if s in outputs:
 6         outputs.remove(s)  #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉
 7     inputs.remove(s)    #inputs中也删除掉
 8     s.close()           #把这个连接关闭掉
 9  
10     # Remove message queue
11     del message_queues[s]

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

 1 # Handle outputs
 2 for s in writable:
 3     try:
 4         next_msg = message_queues[s].get_nowait()
 5     except Queue.Empty:
 6         # No messages waiting so stop checking for writability.
 7         print >>sys.stderr, 'output queue for', s.getpeername(), 'is empty'
 8         outputs.remove(s)
 9     else:
10         print >>sys.stderr, 'sending "%s" to %s' % (next_msg, s.getpeername())
11         s.send(next_msg)

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

 1 # Handle "exceptional conditions"
 2 for s in exceptional:
 3     print >>sys.stderr, 'handling exceptional condition for', s.getpeername()
 4     # Stop listening for input on the connection
 5     inputs.remove(s)
 6     if s in outputs:
 7         outputs.remove(s)
 8     s.close()
 9  
10     # Remove message queue
11     del message_queues[s]

3 完整的server端和client端示例

  这里实现了一个server,其功能就是可以和多个client建立连接,每个client的发过来的数据加上一个response字符串返回给client端~~~

server端:

 1 #! /usr/bin/env python3
 2 # -*- coding:utf-8 -*-
 3 import socket
 4 import select
 5 
 6 sk = socket.socket()
 7 sk.bind(('127.0.0.1', 9000),)
 8 sk.listen(5)
 9 
10 inputs = [sk, ]
11 outputs = []
12 message = {}  # 实现读写分离
13 print("start...")
14 
15 while True:
16     # 监听的inputs中的socket对象内部如果有变化,那么这个对象就会在rlist
17     # outputs里有什么对象,wlist中就有什么对象
18     # []如果这里的对象内部出错,那会把这些对象加到elist中
19     # 1 是超时时间
20     rlist, wlist, elist = select.select(inputs, outputs, [], 1)
21     print(len(inputs), len(outputs))
22 
23     for r in rlist:
24         if r == sk:
25             conn, addr = sk.accept()
26             conn.sendall(b"ok")
27             # 这里记住是吧conn添加到inputs中去监听,千万别写成r了
28             inputs.append(conn)
29             message[conn] = []
30         else:
31             try:
32                 data = r.recv(1024)
33                 print(data)
34                 if not data:
35                     raise Exception('连接断开')
36                 message[r].append(data)
37                 outputs.append(r)
38             except Exception as e:
39                 inputs.remove(r)
40                 del message[r]
41 
42     for r in wlist:
43         data = str(message[r].pop(), encoding='utf-8')
44         res = data + "response"
45         r.sendall(bytes(res, encoding='utf-8'))
46         outputs.remove(r)
47 # 实现读写分离
48 # IO多路复用的本质是用select、poll、epoll(系统底层提供的)来监听socket对象内部是否有变化
49 # select 是在Win和Linux中都支持额,相当于系统内部维护了一个for循环,缺点是监听个数有上限(1024),效率不高
50 # poll的监听个数没有限制,但仍然用循环,效率不高。
51 # epoll的机制是socket对象变化,主动告诉epoll。而不是轮询,相当于有个回调函数,效率比前两者高
52 # Nginx就是用epoll。只要IO操作都支持,除开文件操作
53 
54 # 列表删除指定元素用remove

client端:

 1 #! /usr/bin/env python3
 2 # -*- coding:utf-8 -*-
 3 
 4 import socket
 5 
 6 
 7 sc = socket.socket()
 8 sc.connect(("127.0.0.1", 9000,))
 9 
10 
11 data = sc.recv(1024)
12 print(data)
13 while True:
14     msg = input(">>>:")
15     if msg == 'q':
16         break
17     if len(msg) == 0:
18         continue
19 
20     send_msg = bytes(msg, encoding="utf-8")
21     sc.send(send_msg)
22     res = sc.recv(1024)
23     print(str(res, encoding="utf-8"))
24 sc.close()

猜你喜欢

转载自blog.csdn.net/weixin_39561473/article/details/86770854