网络IO-阻塞、非阻塞、IO复用、异步

  网络socket输入操作分为两个阶段:等待网络数据到达和将到达内核的数据复制到应用进程缓冲区。对这两个阶段不同的处理方式将网络IO分为不同的模型:IO阻塞模型、非阻塞模型、多路复用和异步IO。本文可运行代码链接:https://github.com/killianxu/network_example

一 阻塞模型

  阻塞模型原理如下图1.1,当进行系统调用recvfrom时,应用进程进入内核态,内核判断是否已收到数据报,若没有则阻塞直到数据报准备好,接着复制数据到应用进程缓冲区,然后函数返回。

 


图1.1 阻塞IO模型


阻塞模型缺点:若数据报未准备好,则线程阻塞,不能进行其它操作和网络连接请求。
利用多进程多线程方案,为每个连接创建一个进程或线程,这样一个线程的阻塞不会影响到其它连接,但当遇到连接请求比较多时,会创建较多的进程或线程,严重浪费系统资源,影响进程的响应效率,进程和线程也更容易进入假死状态。
利用线程池或连接池,可以减少资源消耗。线程池利用已有线程,减少线程频繁创建和销毁,线程维持在一定数量,当有新的连接请求时,重用已有线程。连接池尽量重用已有连接,减少连接的创建和关闭。线程池和连接池一定程度上缓解频繁IO的资源消耗,但线程池和连接池都有一定规模,当连接请求数远超过池上线,池系统构成的响应并不比多线程方案好多少。[1]
阻塞模型python实例demo如下:
阻塞模型server端

复制代码
def start_blocking(self):         """同步阻塞server"""         self.ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)         self.ssock.bind(('', 8080))         self.ssock.listen(5)         count = 0         while True:             conn, addr = self.ssock.accept()             count += 1            print 'Connected by', addr             print 'Accepted clinet count:%d' % count             data = conn.recv(1024) #若无数据则阻塞            if data:                 conn.sendall(data)             conn.close()
复制代码

  阻塞模型client

复制代码
def start_blocking(self):         self.host = '123.207.123.108'         self.port = 8080         self.csock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)         self.csock.connect((self.host, self.port))         data = self.csock.recv(1024)         print data
复制代码

  运行server端,并运行两个client实例去连接服务端,运行结果如下图1.2,可以看到虽然有两个客户端去连接,但却只有一个连接上,服务端的socket conn为阻塞套接字,conn.recv(1024)未收到客户端发送的数据,处于阻塞状态,服务端无法再响应另一个客户端的连接。

图1.2 阻塞IO服务端运行结果

二 非阻塞模型

  由于阻塞IO无法满足大规模请求的缺点,因此出现了非阻塞模型。非阻塞IO模型如下图1.3所示,当数据报未准备好,recvfrom立即返回一个EWOULDBLOCK错误,可以利用轮询不停调用recvfrom,当数据报准备好,内核则将数据复制到应用进程缓冲区。

图1.3 非阻塞IO模型


非阻塞IO模型需要利用轮询不断调用recvfrom,浪费大量CPU时间,且当内核接收到数据时,需要等到下一次轮询才能复制到应用进程缓冲区,数据得不到立刻处理。
非阻塞模型python demo如下:
非阻塞服务端

复制代码
def start_noblocking(self):         """         同步非阻塞         """         self.ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)         self.ssock.bind(('', 8080))         self.ssock.listen(5)         count = 0         while True:             conn, addr = self.ssock.accept()             conn.setblocking(0) #设置为非阻塞socket            count += 1            print 'Connected by', addr             print 'Accepted clinet count:%d' % count             try:                 data = conn.recv(1024) #非阻塞,没有数据会立刻返回                if data:                     conn.sendall(data)             except Exception as e:                pass            finally:                 conn.close()
复制代码

  运行非阻塞服务端和两个客户端实例,结果如下图1.4所示,服务端接收两个连接请求。由于conn被设置为非阻塞socket,即使客户端并没有向服务端发送数据,conn.recv(1024)也会立即返回,不会阻塞,从而进程可以接收新的连接请求。

图1.4 非阻塞服务端运行结果

三 IO复用

  IO复用在linux中包括select、poll、epoll模型三种,这三个IO复用模型有各自的API实现,以select模型为例,调用select函数,进程

50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信