一、阻塞IO
应用程序调用IO函数,导致应用程序阻塞,等待数据准备好。如果数据没有准备好,一直等待数据准备好了,从内核拷贝到用户空间,IO函数返回成功指示。
优点:及时返回数据,无延时,方便调试;
缺点:需要付出等待的代价。
二、非阻塞IO
应用程序调用IO函数,并不需要等待,而是马上就得到了一个结果。一旦内核中的数据准备好了,并且又再次收到了请求,那么它马上就将数据拷贝到了用户线程,然后返回。
优点:不用等待任务,当前线程同时处理多任务;
缺点:任务完成的响应延时增大,因为每隔一段时间才去执行询问的动作。
三、IO多路复用(I/O multiplexing)
IO多路复用就是在单个线程里,通过跟踪多个IO流的状态来同时管理多个IO流,简单的说就是在同一个线程里面, 通过拨开关的方式,来同时传输多个I/O流。Linux下IO多路复用的基本方法包括select、poll、epoll,其中epoll是poll的加强版。
1.select中的主要函数:
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就绪描述符的数目,超时返回0,出错返回-1
设置fd_set的宏:
void FD_ZERO(fd_set *fdset); //清空集合
void FD_SET(int fd, fd_set *fdset); //将一个给定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset); //将一个给定的文件描述符从集合中删除
int FD_ISSET(int fd, fd_set *fdset); // 检查集合中指定的文件描述符是否可以读写
使用时注意,每次select要清空并重新设置fd_set
FD_ZERO(fds);//fds表示需要监测的文件标识符集合
FD_SET(clientfd, fds);//clientfd表示监测的文件句柄
2.poll中的主要函数:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件
struct pollfd{
int fd; //文件描述符
short events; //等待的事件
short revents; //实际发生的事件
};
events:指定监测fd的事件(输入POLLIN、输出POLLOUT、错误POLLERR)
revents:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回.
监测输入:(fds[0].revents & POLLIN ) == POLLIN // 标准输入
3.epoll中的主要函数:
int epoll_create(int size);
返回:若成功返回文件描述符,若出错返回-1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
返回:若成功返回0,若出错返回-1
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
返回:成功返回就绪态文件描述符数目,超时返回0,若出错返回-1
参数ev是指向结构体epoll_event的指针,结构体的定义如下。
struct epoll_event
{
uint32_t events; /* epoll events(bit mask) */
epoll_data_t data; /* User data */
};
结构体epoll_event中的data字段的类型为:
typedef union epoll_data
{
void *ptr; /* Pointer to user-defind data */
int fd; /* File descriptor */
uint32_t u32; /* 32-bit integer */
uint64_t u64; /* 64-bit integer */
}epoll_data_t;
四、信号驱动式IO
信号驱动IO是指:进程预先告知内核,使得 当某个socketfd有events(事件)发生时,内核使用信号通知相关进程。
信号驱动IO模型主要是在UDP套接字上使用,在TCP套接字上几乎是没有什么使用的。在UDP上,SIGIO信号会在下面两个事件的时候产生:
1.数据报到达套接字
2.套接字上发上一部错误
因此我们很容易判断SIGIO出现的时候,如果不是发生错误,那么就是有数据报到达了。
而在TCP上,由于TCP是双工的,它的信号产生过于平凡,并且信号的出现几乎没有告诉我们发生了什么事情。因此对于TCP套接字,SIGIO信号是没有什么使用的。
以上四种都属于同步IO模型,接下来第五种是最常用的异步模型。
五、异步式IO
用户程序可以通过向内核发出I/O请求命令,不用等待I/O事件真正发生,可以继续做另外的事情,等I/O操作完成,内核会通过函数回调或者信号机制通知用户进程。这样很大程度提高了系统吞吐量。
目前比较知名的有 Glibc 的 AIO 与 Kernel Native AIO,Kernel Native AIO 几乎提供了近乎完美的异步方式,但
目前的Kernel AIO 仅支持 O_DIRECT 方式来对磁盘读写,这意味着,你无法利用系统的缓存,同时它要求读写的的大小和偏移要以区块的方式对齐,所以现在Linux 上,没有比较完美的异步IO方案。 (AIO不支持fsync,AIO如果操作socket fd,不会提示错误,但是也会以同步的方式进行。)