以下内容基于python2.7。
(一)python非阻塞网络编程
非阻塞网络编程要求监听或等待接受不阻塞当前线程,如果资源没到就先跳过(其实是抛出IOError
)继续执行后面的代码。
- 非阻塞监听:
sock.setblocking(False)
- 非阻塞接收:
conn.setblocking(False)
示例:
- 服务端
# coding: utf-8 import socket CONN_ADDR = ('127.0.0.1', 9999) conn_list = [] # 连接列表 sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 开启socket sock.setblocking(False) # 设置为非阻塞 sock.bind(CONN_ADDR) # 绑定IP和端口到套接字 sock.listen(5) # 监听,5表示客户端最大连接数 print('start listen') while True: try: conn, addr = sock.accept() # 被动接受TCP客户的连接,等待连接的到来,收不到时会报异常 print('connect by ', addr) conn_list.append(conn) conn.setblocking(False) # 设置非阻塞 except IOError: pass # 非阻塞connect时如果没有connect发起则抛出异常 tmp_list = [conn for conn in conn_list] for conn in tmp_list: try: data = conn.recv(1024) # 接收数据1024字节 if data: print u'收到的数据是%s' % data conn.send(data) else: print('close conn',conn) conn.close() conn_list.remove(conn) except IOError: pass # 非阻塞recv时如果没有recv成功则抛出异常
- 客户端
# coding: utf-8 import socket client = socket.socket() client.connect(('127.0.0.1', 9999)) while True: try: msg = input(">>>") if msg != 'q': client.send(unicode(msg)) data = client.recv(1024) print u'收到的数据%s' % data else: client.close() print('close client socket') break except SyntaxError: # 空输入 pass
(二)多路复用IO
阻塞I/O只能阻塞一个I/O操作,而I/O复用模型能够阻塞多个I/O操作。
epoll是Linux效率最高的IO多路复用技术,而Windows只有默认的select。
注意:python2没有selectors
模块,需要先pip install selectors2
再使用selectors2
模块替代。
使用IO多路复用的服务器示例:
# coding: utf-8
import socket
import selectors2
# 注册一个epllo事件
# 1. socket
# 2.事件可读
# 3.回调函数 把一个函数当成变量传到函数里
def recv_data(conn):
try:
data = conn.recv(1024)
if data:
print u'接收的数据是:%s' % data
conn.send(data)
else:
e_poll.unregister(conn)
conn.close()
except IOError:
pass
def acc_conn(p_server):
conn, addr = p_server.accept()
print 'Connected by', addr
# 也有注册一个epoll
e_poll.register(conn, selectors2.EVENT_READ, recv_data)
CONN_ADDR = ('127.0.0.1', 9999)
server = socket.socket()
server.setblocking(False)
server.bind(CONN_ADDR)
server.listen(6) # 表示一个客户端最大的连接数
# 生成一个epllo选择器实例 I/O多路复用,监控多个socket连接
# e_poll = selectors2.EpollSelector() # linux特有
e_poll = selectors2.DefaultSelector() # window没有epoll
e_poll.register(server, selectors2.EVENT_READ, acc_conn)
# 事件循环
while True:
# 事件循环不断地调用select获取被激活的socket
events = e_poll.select(timeout=1)
print events
for key, _ in events:
call_back = key.data
call_back(key.fileobj)
如果不喜欢多路复用IO阻塞当前线程,可以给select
方法传递一个timeout
参数设置等待时间。
(三)异步非阻塞网络编程asyncroe
具体API文档可参考17.6. asyncore — Asynchronous socket handler — Python 2.7.18 documentation。
- 基类:
dispatcher
- 提供方法:
accept, read, write, close
- 可重写方法:
readable, writable, handle_accept, handle_read, handle_write, handle_close
- 原理:
- 模块维护一个全局变量
socket_map
字典保存所有的dispatcher
实例,并在loop
方法中轮询。def loop(timeout=30.0, use_poll=False, map=None, count=None): if map is None: map = socket_map if use_poll and hasattr(select, 'poll'): poll_fun = poll2 else: poll_fun = poll if count is None: while map: poll_fun(timeout, map) else: while map and count > 0: poll_fun(timeout, map) count = count - 1
- 综合每个
dispatcher
对象的writable, readable
等方法和属性,分别得出做好读、写、异常的dispatcher
列表,并使用IO多路复用进行相应的操作(非阻塞),得到最终进行了读、写、异常的dispatcher
列表,执行相对应的handle响应方法。以下仅列出read
作为示例,write
与except
等其他过程类似。def poll(timeout=0.0, map=None): r, w, e = select.select(r, w, e, timeout) for fd in r: obj = map.get(fd) if obj is None: continue read(obj) def read(obj): try: obj.handle_read_event() except _reraised_exceptions: raise except: obj.handle_error()
- 模块维护一个全局变量
【参考资料】
[1] Python网络编程-IO阻塞与非阻塞及多路复用
[2] Python异步非阻塞IO多路复用Select/Poll/Epoll使用
[3] 17.6. asyncore — Asynchronous socket handler — Python 2.7.18 documentation
[4] Python asyncore异步socket封装模块用法总结 - 小西红柿 - 博客园
[5] Python中的asyncore - 小西红柿 - 博客园
[6] Python 2.7.15 中文文档 - 16.1. 选择—awaitI/O 完成 | Docs4dev