---
layout: post
title: "计算机网络IO模型原理性解析"
category: Linux 
tags: [net]
---

&emsp;&emsp;本文旨在原理性的揭示计算机网络IO模型以及select、epoll的区别，而并不探讨实现细节，另外，讨论阻塞与非阻塞、同步和异步时往往是上下文相关的，本文的上下文就是Linux IO，下文中IO操作就以read为例。  
首先明确一点，一次IO操作分为两个阶段：

1. 等待数据准备就绪
2. 将数据从内核拷贝到用户进程空间  

* **阻塞IO：**  
        &emsp;&emsp;首先用户进程发起read系统调用，内核开始准备数据，一般的，对于网络IO来说，大部分情况下当用户进程发起read系统调用的时候数据尚未到达，这个时候内核一直等待数据到达，在用户进程这一侧，整个进程会被阻塞。当数据到达后，内核就把准备妥当的数据从内核空间拷贝用户进程空间，然后内核返回，用户进程被唤醒，因此，阻塞IO在一次IO操作的两个阶段均被阻塞了。

* **非阻塞IO：**  
        &emsp;&emsp;当用户进程发起read系统调用时，如果内核还没收到数据，则此次系统调用立即返回一个错误而不是阻塞整个进程，用户进程需要通过此次调用的返回值判断是否读到了数据，如果没有则隔一段时间再次发起read调用，如此不断轮询，在用户进程轮询期间，内核一直在等待数据到达，当数据准备妥当后，当用户进程发起当read系统调用时，内核负责将数据拷贝到用户进程空间，直到拷贝完成，read操作返回，也就是在这一阶段（第二阶段），非阻塞IO表现出的其实是一个同步操作，因此，从内核拷贝数据到用户进程空间的这段时间内read调用是一直等待直到完成的，所以，非阻塞IO是属于同步IO，也就是说非阻塞IO操作在第一阶段是不阻塞的，在第二阶段是阻塞的。

* **同步IO：**  
        &emsp;&emsp;一个同步IO操作会导致进程阻塞直到IO操作完成，所以，阻塞IO、非阻塞IO以及本文未提及到的IO多路复用都属于同步IO。

* **异步IO：**  
        &emsp;&emsp;一个异步IO操作不会引起进程的阻塞。


>  &emsp;&emsp;这里面大家可能分不清阻塞和非阻塞IO，甚至会认为非阻塞就是同步了，其实不是的，回到本文开头，我们说一次IO操作分两个阶段，第一阶段是等待数据，第二阶段拷贝数据（内核到用户空间），非阻塞IO操作在数据还没有到达时是立即返回到，这一点表现的跟异步一样，但是，在第二阶段，当数据准备妥当的时候，非阻塞IO是阻塞的，直到数据拷贝完成，这一点表现的与同步IO完全不一样，异步操作根本不关心你的数据是否到达以及你的数据是否拷贝完成，异步发起一次操作后就去干别的事儿了，直到内核发出一个信号通知这一事件，它才会转回来继续处理。


* **select与epoll**  
        &emsp;&emsp;select与epoll都是IO多路复用模型，它们都是监视一组文件描述符（在本文就是socket）上是否有数据准备就绪，select和epoll会一直阻塞直到它们监视的一个或多个套接字描述符上数据准备就绪，当在某个或某几个套接字描述符上有数据到达时，对select而言，它需要遍历这组描述符以确定到底是哪一个描述符上面有数据到达了，所以时间复杂度是O(n)，而对于epoll而言，它不是通过遍历或者叫轮询的方式来确定数据准备妥当的描述符，而是通过事件的方式，以事件的方式通知epoll是哪个描述符上数据到达来，因此，时间复杂度是O(1)。


> &emsp;&emsp;可能说的很啰嗦，只是想尽量的把原理讲清楚，原理清楚来再看实现细节才能做到游刃有余。

* **参考文章:**  
        &emsp;&emsp;[http://blog.csdn.net/historyasamirror/article/details/5778378](http://blog.csdn.net/historyasamirror/article/details/5778378)  
        &emsp;&emsp;[http://yaocoder.blog.51cto.com/2668309/888374](http://yaocoder.blog.51cto.com/2668309/888374)


