以下内容基于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()