Python实现socket的非阻塞式编程

Author Avatar
WoodyXiong 6月 15, 2018
  • 在其它设备中阅读本文章

阻塞模式与非阻塞模式

阻塞模式 程序碰到了一些耗时操作,无法继续向下走。

例如在socket编程中,例如在send()即发送信息过程中,可能对方已经断开,可能网络等原因导致信息传递不通畅;在客户端的connect()函数中,可能地址不可达等原因。这些情况在阻塞模式中会造成线程中断等待,导致无法进行下一步操作,等超过一个固定时间还没有完成之后会产生异常。但是这种阻塞通常用于确定的几个连接地址并且必须准确连接上。

非阻塞模式 当程序碰到耗时操作,分发给别的线程,主线程继续执行。

例如在socket编程中,在send()connect()函数中,程序会抛出异常10035,在非阻塞模式下无法完成耗时操作,但是程序会继续走下去,不会阻塞到当前的程序。那么,怎么判断什么时候程序完成这些耗时操作呢?select闪亮登场。

Python网络编程-IO阻塞与非阻塞及多路复用

基于select的网络编程

在python中,select函数是一个队底层操作系统直接访问的接口,它用来监控socketsfilespipse,等待IO完成。当有可读、可写或者是异常事件产生时,select函数可以监控到。

r, w, e, = select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。这个就是系统级别的阻塞,如果监控到事件会直接传递到这里。

具体的客户端非阻塞连接如下,使用的是python3.6

# -*- coding: UTF-8 -*-
# python使用select进行非阻塞模式编程,客户端程序
import socket
import select

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    # 生成socket
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 不经过WAIT_TIME,直接关闭
sock.setblocking(False)                                     # 设置非阻塞编程

try:
    # sock.connect(("google.com", 80))
    sock.connect(("192.168.1.106", 789))
except Exception as e:
    print(e)

r_inputs = set()
r_inputs.add(sock)
w_inputs = set()
w_inputs.add(sock)
e_inputs = set()
e_inputs.add(sock)

while True:
    try:
        r_list, w_list, e_list = select.select(r_inputs, w_inputs, e_inputs, 1)
        print("r")              # 产生了可读事件,即服务端发送信息
        for event in r_list:
            try:
                data = event.recv(1024)
            except Exception as e:
                print(e)
            if data:
                print(data)
                print("收到信息")
            else:
                print("远程断开连接")
                r_inputs.clear()
        print("w")
        if len(w_list) > 0:     # 产生了可写的事件,即连接完成
            print(w_list)
            w_inputs.clear()    # 当连接完成之后,清除掉完成连接的socket
        print("e")
        if len(e_list) > 0:     # 产生了错误的事件,即连接错误
            print(e_list)
            e_inputs.clear()    # 当连接有错误发生时,清除掉发生错误的socket
    except OSError as e:
        print(e)

非阻塞服务端代码如下,同样使用python3.6

# -*- coding: UTF-8 -*-
import socket
import select

sock = socket.socket()
sock.bind(('192.168.1.106', 789))
sock.setblocking(False)
sock.listen()


inputs = [sock, ]

while True:
    r_list, w_list, e_list = select.select(inputs, [], [], 1)
    for event in r_list:
        if event == sock:
            print("新的客户端连接")
            new_sock, addr = event.accept()
            inputs.append(new_sock)
        else:
            data = event.recv(1024)
            if data:
                print("接收到客户端信息")
                print(data)
                event.send(b'\x31')
            else:
                print("客户端断开连接")
                inputs.remove(event)

python的select注意事项

除了select,还有什么提高效率的神器

select,poll,epoll优缺点及比较

深度理解select、poll和epoll

通过上述文章可知效率对比 epoll > poll > select

但是在windows系统上只有select,所以常常用以下判断

        if hasattr(select, 'epoll'):
            self._impl = select.epoll()
            model = 'epoll'
        elif hasattr(select, 'kqueue'):
            self._impl = KqueueLoop()
            model = 'kqueue'
        elif hasattr(select, 'select'):
            self._impl = SelectLoop()
            model = 'select'

切记如果在非阻塞情况下缠上了10035的错误,那是正常反应,我们只需用select进行获取即可。(我不会告诉你我被这个坑折腾了好几天)

python官方也推荐使用select来提高socket的效率