Python高级网络编程系列之第二篇

时间:2021-06-17 18:00:56

  在上一篇中,我们深入探讨了TCP/IP协议的11种状态,理解这些状态对我们编写服务器的时候有很大的帮助,但一般写服务器都是使用C/Java语言,因为这些语言对高并发的支持特别好。我们写的这些简单的服务器主要是为了深入学习TCP/IP协议、IO操作以及Python中协程的原理。在上一篇中也提到非阻塞这个概念,在这一篇中,我们继续深入探讨IO模型,因为理解IO操作对我们深入学习异步编程有很大帮助。所以在这一节中我们主要是从Linux内核态和用户态的层面来考虑IO操作时会发生什么样的事情,Linux内核会做什么。

 

一、常见5种IO模型

1.阻塞IO模型

    阻塞IO模型图如下:

 Python高级网络编程系列之第二篇

  说明:

  (1).当上层应用程序调用recv系统调用时,进程会从用户态切换到内核态;如果此时对方没有发送数据来(内核缓冲区没有数据),那么应用程序将会被阻塞(默认行为,被Linux内核阻塞)。

  (2).当对方把数据发送过来了之后,Linux内核会自动把数据copy到用户空间的缓冲区中,然后进程恢复执行,执行下一步操作!

 

2.非阻塞IO模型

  非阻塞IO模型图如下:

Python高级网络编程系列之第二篇

  说明:

  (1). 进程中需要将套接字设置为非阻塞模式;

  (2). 进程会一直调用recv()函数去接收数据,如果缓冲区中没有数据,那么进程也不会被阻塞,只是recv()会返回一个错误码(EWOULDBLOCK)

  (3). 进程会不断轮询有没有数据到来。这样会造成进程忙等待。大量消耗CPU资源。因此很少使用,一般和select或epoll机制一起使用。

 

3.IO复用模型

  IO复用模型图如下:

 Python高级网络编程系列之第二篇

  说明:

  (1). 进程使用select机制(该机制由Linux内核支持,避免了进程忙等待),目的是去轮询文件描述符的状态变化

  (2). 当select管理的文件描述符没有变化时,进程也会被阻塞;但是使用select可以管理多个文件描述符,效率就提高了。这就不像非阻塞模型中,去轮询recv()。

  (3). select可以看成一个管家,使用select来管理多个IO。

    一旦检测到一个或多个IO有我们感兴趣的事件发生时,select函数将返回,返回值是检测到的事件个数。

  (4). select函数可以设置超时时间, 这样可以避免进程处于干等待状态,长期僵死

  (5). 和非阻塞IO模型相比,select IO复用模型相当于提前阻塞了。当有数据来到时,可以直接调用recv()来获取数据。

 

4.信号驱动IO

  信号驱动IO模型图如下:

Python高级网络编程系列之第二篇

  说明:

  (1). 在上层应用程序中会建立SIGIO信号的处理程序。当缓冲区有数据来到时,内核会发送数据到上层应用程序。

  (2). 当上层应用程序收到信号后,调用recv()函数,因为缓冲区有数据,recv()函数一般不会被阻塞。

  (3). 这种模型用的比较少,属于典型的"拉模型",即,上层应用程序需要调用recv()函数把数据拉进来。

 

5.异步IO模型

  异步IO模型图如下:

Python高级网络编程系列之第二篇

  说明:

  (1). 上层应用程序调用aio_read函数,同时会提交一个应用层的缓冲区给内核写入数据;调用完毕后,应用程序不会被阻塞,可以继续执行其他任务。

  (2). 当数据过来时,Linux内核主动把数据从内核空间copy到用户空间,然后再给应用程序发送一个信号,告诉进程数据已经复制过去了,你赶紧处理数据吧。

  (3). 这是一个"推模式",效率非常之高,应用程序有异步处理的能力(在Linux内核的辅助下,进程在处理其他任务的同时,也可以进行IO通讯)。与信号驱动IO模型相比,应用程序不需要调用recv()来接收数据!

  (4). 异步IO是什么?

    应用程序在处理其他事情的同时,还可以接收数据。

 

小结:通过对比五种IO模型,我们可以很明显发现他们的区别以及效率,一般在开发中IO复用模型和异步IO模型是最常用的模型!

   五种模型的对比图:

   Python高级网络编程系列之第二篇