序言
本期文章是一个序列,是对工作以及学习中关于网络编程的一个总结,本序列将从一个最简单的网络程序开始介绍当前网络编程的递进过程,有兴趣的朋友可以去阅读关于C10K问题的文档。在开始代码介绍之前,本文先介绍一些网络编程的基本概念,本文不会对socket API作介绍,如socket(),accpet()等。相信有兴趣浏览该文章的朋友应该对这些函数的使用已经有了基本的了解,要是不了解的话可以自行baidu google。
网络编程中的基本概念
同步(SYNC)
所谓同步是指程序在进行函数或者功能调用时,在没有得到结果前函数不会返回,系统需等待该函数返回,就比如说你做一件事情分为几个步骤,在一个步骤没有完成前不能进行下一个步骤。在网络编程中就体现为,客户端给服务器发送一个请求,客户端需要等待服务端回复响应才能继续处理往下处理。
异步(ASYNC)
所谓的异步跟上面的同步刚好相反,程序在进行函数或者系统调用后立即返回,调用者不清楚调用结果,需要等到处理结束后以回调函数、信号、状态通知等方式来通知调用者已经处理完毕。网络编程中体现为,客户端给服务器发送一个请求,调用立即返回,客户端立即处理其他业务,带服务端处理完毕后给客户端发送通知,告诉客户端处理结果。
阻塞(Block)
所谓的阻塞是指调用者调用网络编程中的一些函数的时,当前线程立即被挂起,线程进入非就绪状态,CPU不再给该线程分配时间片,只有在函数返回时该线程才能够得到CPU分配的时间片,并继续执行。
同步和阻塞的概念比较相似,很多人都认为同步和阻塞是一样的,包括我刚开始也是这样认为,其实两者是完全不同的。同步状态下,调用线程是激活的,线程并未挂起,而阻塞模式下,线程是挂起的,得不到CPU分配的时间片。
非阻塞(NonBlock)
非阻塞跟阻塞也刚刚相反,调用不会阻塞当前线程,调用立即返回。
同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞!
阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回!
阻塞和非阻塞是指当进程访问的数据如果尚未就绪,进程是否需要等待,简单说这相当于函数内部的实现区别,也就是未就绪时是直接返回还是等待就绪;
而同步和异步是指访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后在读写的时候必须阻塞(区别就绪与读写二个阶段,同步的读写必须阻塞),异步则指主动请求数据后便可以继续处理其它任务,随后等待I/O,操作完毕的通知,这可以使进程在数据读写时也不阻塞。(等待"通知"),简单的说同步和异步是针对数据而言的,同步需要等待数据拷贝完成,而异步不需要等待数据拷贝完成。而阻塞和非阻塞是针对调用而言的,函数调用立即返回就是非阻塞,而函数需要等待其他才做完成后才返回就是阻塞的
Linux 下的五种IO模型
关于IO模型的基本介绍可参数UNIX网络编程这本网络编程入门级教材,里面写的很详细,强烈建议想从事网络编程的朋友们这本书一定必读。
阻塞式IO模型
阻塞式IO模型在系统调用过程中,应用程序会一直阻塞,等待数据准备好并且从内核空间拷贝到用户空间,函数才返回。
阻塞式IO模型图:
应用程序调用系统调用recv/recvfrom时,首先会检查内核是否有数据准备好,如果内核没有数据准备好,那么系统处于等待状态,当前线程会被挂起。当内核数据准备好后,系统将数据从内核空间复制到用户空间,系统调用立即返回。在socket编程过程中,当调用recv函数准备接收数据时,若没有数据到达,应用程序会处于等待状态。
Linux下socket()函数默认创建的套接字都是阻塞的,这意外着socket API进行系统调用不能立即返回时,应用线程处于等待状态,然而并不是所有已阻塞模式调用的socket API当函数不能立即返回时,应用线程都是处于阻塞状态,比如socket()函数、listen()、bind()都是立即返回调用结果的。可能阻塞的socket包括以下几种:
1、输入操作:recv()、recvfrom,若以阻塞式套接字调用该输入操作API,当系统没有数据准备好时,线程处于睡眠状态,直到数据准备好。
2、输出操作:send()、sendto(),若以阻塞式套接字调用时,若输出缓冲区内没有数据可写,则线程处于睡眠状态,直接输出缓冲区可写。
3、接受连接:accpet(),若以阻塞式套接字调用时,直接阻塞,直到有客户端向该socket发起连接请求。
4、发起连接:connect(),若以阻塞式套接字调用时,发起连接方需等待直接被连接方告诉连接方连接结果。
阻塞式套接字实现起来比较简单,比较适合连接少的,且处理数据比较少的情况下使用该模式比较适合,但是当需要处理的大量连接时,该模式弊端的明显了。需要极大的系统开销。
非阻塞式IO模型
将一个套接字设置为非阻塞式告诉内核,当所请求的I/O操作非得把本进程或线程投入睡眠时不要把本进程或线程投入睡眠,而是返回一个错误。这种场景下我们的应用程序会不断去查看数据是否准备好,如果没有准备好则继续测试,在这个过程中我们会频繁的发起系统调用查看数据是否准备好,浪费CPU时间。
非阻塞式IO模型图:
应用进程在非阻塞模式下发起recv()/recvfrom()调用,当内核没有数据准备好时,调用立即返回一个错误,应用进程就可以通过内核返回的错误判断当前是内核没有数据准备好,然后应用进程不断的去查询内核数据是否准备好,当内核数据准备好并拷贝到用户空间后调用返回成功。
非阻塞IO模型实现上比阻塞式IO模型复杂,但是非阻塞式IO模型在处理大量连接,且数据量不等的情况下具有明显的优势。
I/O复用模型
有了I/O复用模型后(epoll、select...),应用程序就可以在一个线程进程中一次监听多个套接字,检查套接字状态可读可写,节省了一个线程处理一个连接所需的线程资源,以及线程切换的开销。
IO复用模型中,进程也会阻塞于select,epoll等系统调用,但该系统调用一次可以检查多个套接字状态,当该系统调用返回时,说明内核已经有数据准备好,应用进程可以调用socket调用去读写数据。I/O复用模型为编写高并发网络程序提供了实现手段。
信号驱动式I/O模型
使用信号驱动式IO模型的前提是应用进程需要为所需的信号注册一个信号处理函数,当内核在数据准备好时向应用进程发送该信号,应用进程收到信号后再信号处理函数中调用真正的socket调用去内核读写数据。
信号驱动式IO模型图
信号驱动式I/O模型的优势是应用进程在等于数据到达时进程不会被阻塞,主循环可以继续处理其他业务,只有收到内核发送的指定信号通知时再进行socket系统调用去读写数据。
异步I/O模型(Asynchronous IO, AIO)
异步I/O的机制是告诉内核启动某个操作,并让内核在整个过程(包括将数据从内核拷贝到用户空间)完成后通知我们。
需要注意的是异步IO和信号驱动式IO的区别是,信号驱动式IO是内核通知我们何时启动一个操作,而异步IO是内核告诉我们IO操作何时完成,这是本质区别。
五种IO模型的比较
可以发现前四种IO模式都是同步型IO模型,因为真正的IO操作recv() recvfrom都将阻塞进程。
以上就是一些网络编程中的基本概念,希望想入门的朋友能够理解这些概念的意思,尤其是同步,异步,阻塞,非阻塞的基本概念。