IO概述
1.同步IO与异步IO
同步IO
:必须等待IO操作完成后,控制权才返回给用户进程
异步IO
:无须等待IO操作完成,就将控制权返回给用户进程
2.常见的网络IO
- 输入操作:等待数据到达套接字接受缓冲区
- 输出操作:等待套接字发送缓冲区有足够的空间容纳将要发送的数据
- 服务器接收连接请求:等待新的客户端连接请求的到来
- 客户端发送连接请求:等待服务器回送客户发起的SYN所对应的ACK
3.网络IO发生时经历的过程
- 涉及两个系统对象,(1)调用这个IO的进程 (2)系统内核
- 当一个read(假设IO为read)发生时,会经历两个阶段:(1)
等待数据准备
(2)将数据从内核拷贝到进程中
4种网络IO模型
为了解决网络IO中的问题,学者们提出了4种网络IO模型:阻塞IO模型、非阻塞IO模型、多路IO复用模型、异步IO模型
阻塞:指IO操作需要彻底完成后才返回到用户空间
非阻塞:指IO操作被调用后立即返回给用户一个状态值,不需要等到IO操作彻底完成
阻塞IO模型
- 阻塞IO模型的特点就是在IO执行的两个阶段(等待数据和拷贝数据)都被阻塞了
- 在阻塞IO模型中,大部分socket接口都是阻塞型的。所谓阻塞型的接口是
指系统调用时却不返回调用结果,并让当前线程一直处于阻塞状态,只有当该系统调用获得结果或者超时出错时才返回结果
;这样会给网络编程带来很大的问题,就当处于该阻塞状态时,想要调用其它函数却由于线程处于阻塞状态而无法响应任何网络请求 - 上述问题的一个解决方案是采用多线程(或线程池),即让每一个连接都有其独立的线程(每一次调用accept返回一个新的socket,可以支持多线程)
- 多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用
非阻塞模型
来尝试解决这个问题
非阻塞IO模型
在linux下,可以通过设置socket使IO变为非阻塞状态。
- 可以看到上述流程中,当用户进程发起一个read操作时,如果内核中的数据尚未准备好,那么立马返回一个错误。从用户进程来说,它发起一个read操作后,并不需要等待,而是马上得到一个结果。如果得到的结果是错误的,那么它就知道此时数据并未准备好,于是可以再发生一个read操作;一旦准备好了,并受到用户进程的调用操作,那么马上将数据复制到用户内存中,然后返回正确的返回值
- 上述方式看起来似乎用户进程并没有被阻塞,但实际用户进程需要不断地询问,内核不断地返回此时状态值,通过recv()函数的返回值判断内核状态:
1.recv()返回值大于0,表示接受数据完毕,返回值即是接收到的字节数
2.recv()返回0,表示连接已经断开
3.recv()返回-1,且errno等于EAGAIN,表示recv()操作还没执行完成
4.recv()返回-1,且errno不等于EAGAIN,表示recv操作遇到系统错误errno
- 这种循环调用recv()将大幅度占用CPU使用率,并不被推荐;可以采用
select()多路复用模式
,不必循环调用recv()函数,而是一次检测多个连接是否活跃
多路IO复用模型
多路IO复用,有时也称事件驱动IO。基本原理是有个函数(select())会不断地轮询所负责地socket,当某个socket有数据到了,就通知用户进程
- 当用户进程调用了select后,整个进程会被阻塞,同时内核会“监视”所有select负责地socket,当任何一个socket中地数据准备ok,select就会返回。这个时候用户进程再调用read操作,将数据从内核拷贝到用户进程
- 在此模型中,进程是被select函数所阻塞
- 有关select函数:
FD_ZERO(int fd, fd_set* fds); //将set清零
FD_SET(int fd, fd_set* fds); //将fd加入set
FD_ISSET(int fd, fd_set* fds); //如果fd在set中则为真
FD_CLR(int fd, fd_set* fds); //将fd从set中清除
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
有关select的参数:
- nfds是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值+1
- readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符
- writefds是指向fd_set结构的指针,这个集合中应该包含文件描述符
- errorfds,用来监视文件错误异常
- timeout:select的超时时间,这个参数尤为重要,置于不同的值可置select于不同的状态 (1)置为NULL,则处于阻塞状态 (2)置为0,则处于非阻塞状态 (3)大于0,则处于超时状态
异步IO模型
上面所介绍的几种都属于同步IO,下面要介绍的这种则是异步IO模型
- 用户进程在发起read操作后,就可以去做其他的事情了;而另一方面,从内核的角度,当收到一个异步的read请求操作后,首先立即返回,所以不会阻塞用户进程;然后,内核等待数据准备完毕,然后将数据拷贝到用户内存中,当这一切完成之后,才向用户进程发送一个信号,返回read操作已完成的信息
- 此IO模型与非阻塞IO模型看上去有点类似,但其实有很大的不同
在非阻塞IO模型中,进程虽不会大部分时间不会被阻塞,但其用户进程必须时刻调用recv()函数去检查内核数据是否准备完成,这样对于进程还是有很大的约束;而在异步IO中,用户进程在发起请求之后,就可以将这个请求托付给“第三方”,自己去做别的事情,当收到内核的信号时,知道自己的操作已经完成,在这个过程中与用户进程完全自由
各个IO模型的比较(信号驱动模型在实际上并不常用,所以在此并未提及):
----------------------------------get----------------------------------------------------
1.4种网络IO模型
2.解决网络IO问题的方式