五种IO模型
1、阻塞IO:在内核将数据准备好之前,系统会一直等待。所有套接字默认方式都是等待,系统在等待期间不参与其他任何活动,直至等到条件成熟。
2、非阻塞IO:如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBACK错误码,系统在等待期间,条件没有成熟时系统可以做其他的事情。非阻塞IO需要程序员以循环的方式反复读写文件描述符,这个过程称作轮询,这对CPU来说是很大的浪费,一般只有特定场景下才可以使用。
3、信号驱动IO:内核将数据准备好时,使用SIGIO信号通知应用程序进行IO操作。内核还没有将数据准备好时,用户可以做其他的事情,当接收到信号时再进行IO处理动作。
4、IO多路转接:IO多路转接能够同时等待多个文件描述符的就绪状态(效率最高)
5、异步IO:由内核在数据拷贝完成时通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据),异步IO只负责发起事件。
IO分两步:第一步叫做等;第二步叫做数据搬迁。要想提高IO的效率,就必须减小IO等的比重。让等待的时间尽量减少
在以上五种IO模型中,前四种都属于同步IO,最后一种属于异步IO。
同步VS异步
所谓同步,就是在发出一个调用时,在没有得到一个结果之前,该调用就不返回,但是一直调用返回,就得到返回值了;换句话说 ,就是由调用者主动等待这个调用的结果。
所谓异步,调用在发出之后,这个调用就直接返回了,所以没有返回结果;换句话说,当一个异步过程调用发出时,调用者不会立刻的到结果,;而是在调用发出后,被调用者通过状态来通知调用者,或者通过回掉函数处理这个调用。
谈同步,首先要明白同步是同步异步当中的同步,还是同步互斥中的同步
阻塞VS非阻塞
阻塞和非阻塞都是指同步IO,只是等的方式不同,阻塞与非阻塞关注的是程序在等待调用结果时的状态。
阻塞调用指调用结果返回之前当前线程会被挂起,调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前进程
非阻塞IO
一个文件描述符默认都是阻塞IO ,要想把一个文件描述符设置为非阻塞,只需要改变cmd的值即可,当cmd=FGETOWN或FSETOWN
void SetNoBlock(int fd)
{
int f1=fcntl(fd,F_GETFL);//获取文件描述符状态(这时一个位图)
if(f1<0)
{
perror("fcntl");
return;
}
fcntl( fd,F_SETFL,f1 | O_NONBLOCK);//将文件描述符设置为非阻塞
}
重定向
dup/dup2系统调用
函数原型如下
#include<stdio.h>
int dup(int oldfd);
int dup2(int oldfd,int newfd);//返回新的文件描述符
使用dup将标准输出重定向到文件中,将没有被使用的最小文件描述符,作为新的文件描述符。
使用dup2将标准输出重定向到文件中,新的文件描述符对应的内容成为旧文件描述符对应内容的拷贝
IO多路转接select
select函数原型
#include<sys/select.h>
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
关于select()文件描述符的返回值
执行成功,返回文件描述符状态已经改变的个数
如果返回0,表示在文件描述符状态改变之前,已经超过timeout时间,没有返回
当有错误发生时,返回-1,错误原因存于error,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。
select 函数只负责等,当select成功返回时,至少有一个文件描述被获取,之后再调用recvfrom.
select函数参数的解释
fdset结构体
#define __FD_SETSIZE 1024
typedef __kernel_fd_set fd_set;
typedef struct {
unsigned long fds_bits[__FD_SETSIZE / (8 *sizeof(long))];
} __kernel_fd_set;
(1)参数nfds是需要监听的所有文件描述符中最大的文件描述符+1,+1的目的是为了限定遍历区间
(2)readfds,writefds,exceptfds分别对应所要检测的可读文件描述符的集合,可写文件描述符的集合,异常文件描述符的集合
(3)参数timeout为结构timeval,用来设置select()等待的时间
关于timeout的取值:
NULL:表示select()没有timeout,select将一直被阻塞,知道某个文件描述符就绪
0:仅检测文件描述符集合的状态,然后立即返回,并不等待外部事件的发生
特定的时间值:如果在指定时间里没有发生,select将超时返回
关于fd_set结构体
fd_set是一个整数数组,本质上是一张位图,比特位的位置决定了对应文件描述符的值
读、写
异常文件描述符既是输入型参数(用户所关心的文件描述符),也是输出型参数(操作系统所关心的那些文件描述符已经就绪)
下面是一些fd_set的接口,来对位图进行操作
void FD_CLR(int fd,fd_set *set);//用来清除词组set中相关fd的位
int FD_ISSET(int fd,fd_set *set);//用来测试描述词组set中相关fd的位是否为真
void FD_SET(int fd,fd_set *set);//用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set);//用来清除描述词组set的全部位
关于timeval结构体
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};
tv_sec 单位是秒
tv_usec 单位是微秒,不是毫秒!毫秒的英文单词是millisecond。
timeval用于描述一段时间长度,如果在这个时间内,需要监听的文件描述符没有就绪则函数返回,并且返回值为0。
socket就绪条件:
读就绪:
socket内核中,接收缓冲区中的字节数,大于等于低水位标SO_RCVLOWAT,此时可以无阻塞的读该文件描述符,并且返回值大于0
socket TCP通信中,对端关闭连接,此时对socket读,则返回0
监听的socket上有新的连接请求。
socket上有未处理的错误
写就绪:
socket内核中,发送缓冲区的可用字节数大于等于低水位标SO_RCVLOWAT,此时可以无阻塞的的写,并且返回值大于0
socket的写操作被关闭,对于一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号
socket使用非阻塞connect连接成功或者失败后
socket上有未读取的错误
异常就绪:
socket上收到带外数据。
select的特点:
(1)可监控的文件描述符个数取决与sizeof(fdset)的值,可监控的文件描述符个数有限
(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd
·一是用于在select返回后,array作为源数据和fdset进行FDISSET判断
·二是select返回后,会把以前加入的但并无事件发生的fd清空,每次开始select之前都要重新从array中取得fd逐一加入,扫描array的同时取得fd的最大值maxfd,用于select的第一个参数
select的缺点
(1)每次调用select,都要手动设置fd集合,从接口角度来说也非常不方便
(2)每次调用select都需要把fd集合从用户态拷贝到内核态,当fd很多时,开销也会很大
(3)每次调用select都需要在内核遍历传递进来的所有fd,当fd很多时,开销也会很大
(4)select支持的文件描述符数量太少
IO多路转接之poll
poll函数的接口
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//pollfd结构体
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
参数说明:
fds是一个poll函数监听的结构列表(数组),每一个元素包含了三部分内容,文件描述符,监听的事件集合,返回的事件集合
nfds表示fds数组的长度
timeout表示poll函数的超时时间,单位是毫秒
events和revents的取值
返回值说明:
返回值小于0,表示出错
返回值等于0,表示poll函数等待超时
返回值大于0,表示poll文件描述符由于监听的文件描述符就绪而返回
socket就绪条件
读就绪:
socket内核中,接收缓冲区中的字节数,大于等于低水位标SO_RCVLOWAT,此时可以无阻塞的读该文件描述符,并且返回值大于0
socket TCP通信中,对端关闭连接,此时对socket读,则返回0
监听的socket上有新的连接请求。
socket上有未处理的错误
写就绪:
socket内核中,发送缓冲区的可用字节数大于等于低水位标SO_RCVLOWAT,此时可以无阻塞的的写,并且返回值大于0
socket的写操作被关闭,对于一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号
socket使用非阻塞connect连接成功或者失败后
socket上有未读取的错误
异常就绪:
socket上收到带外数据。
poll的优点
不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现
pollfd机构包含了要监视的event和发生的event,不再使用select”参数—值”的传递方式,接口使用比select更方便
poll并没有最大数量限制(但数量过大后性能也会下降,底层由用户管理的动态数组表示)
poll的缺点
poll返回后,需要轮询pollfd来获取就绪的文件描述符
每次调用poll都要把大量的pollfd结构从用户态拷贝致内核态
同时连接的大量客户端在同一时刻可能只有很少的文件描述符处于就绪状态,因此随着监视的文件描述符的增长,其效率也会下降