0. 非阻塞式IO

前面一篇博文解释了什么是阻塞式IO,你一定会发现这种IO存在的问题。在等待IO操作完成的过程中,进程会被投入睡眠,只能干等IO操作完成并返回。如果希望内核在进行IO操作的过程中进程继续运行,那么就需要用非阻塞式IO。
进程把一个套接字设置成非阻塞式,就是在通知内核,在你进行IO操作的过程中,不要把进程投入睡眠,IO操作没完成,返回一个错误即可。非阻塞IO的执行流程如下图所示。

这种IO在执行过程中,需要用户进程隔一段时间去调用一次系统调用,不断轮询,直到返回成功。下面看一个例子

1.例子

我们在上一篇博文的例子上做一下改变
服务端程序

import socket               # 导入 socket 模块
import time    
import errno
s = socket.socket()         # 创建 socket 对象
host = socket.gethostname() # 获取本地主机名
port = 12345                # 设置端口
s.bind((host, port))        # 绑定端口
s.listen(5)                 # 等待客户端连接
c, addr = s.accept()        # 建立客户端连接
c.setblocking(False)      #设置与客户端的连接socket为非阻塞式
print("connected from :%s:%s"%(addr[0],addr[1]))
while True:
    print("wating for client input")
    try:
        data = c.recv(1024)
    except socket.error as e:
        err = e.args[0]
        if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
            time.sleep(1)
            print("no data available")
            continue
        else:
            print(e)
            c.close()
            break
    else:
        data = str(data,'utf-8')
        print(data)
        if data == '88':
            c.close()
            break
print("client closed connection")
s.close()

客户端程序


import socket               # 导入 socket 模块

s = socket.socket()         # 创建 socket 对象
host = socket.gethostname() # 获取本地主机名
port = 12345                # 设置端口号

s.connect((host, port))
print("connected to server")
while(True):
    data = input()
    s.send(bytes(data,'utf-8'))
    if data == '88':
        s.close()
        break

服务端建立与客户端的链接后,将socket设置为非阻塞式。对一个非阻塞式socket执行recv()函数,如果成功则返回读取到的数据,如果失败则抛出一个socket异常。
启动服务端程序

启动服务端程序后,客户端程序未启动,服务端阻塞在第9行,等待客户端连接。注意这个socket是服务端监听连接的socket,我们没有设置它非阻塞,默认是阻塞式的,因此程序在此处阻塞住。
然后,我们启动客户端程序

启动客户端程序后,连接成功,服务端读取客户端发来的数据。因为此时客户端没有写数据,因此服务器读取失败,recv()立即返回一个socket error,我们捕获这个error,隔一秒钟打印出"no data available"。
这个就是非阻塞式IO,系统调用在数据没准备好的时候,立即返回。这种IO需要用户程序不断去调用系统调用,这个过程称为轮询。