Python - 非阻塞网络编程

以下内容基于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作为示例,writeexcept等其他过程类似。
      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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值