一、IO模型
IO的方式有阻塞IO、非阻塞IO、IO复用、信号驱动、异步IO等。
1、阻塞IO
阻塞IO是最通用的IO类型,在数据未到来之前程序会一直等待。
2、非阻塞IO
把套接字设置成非阻塞的IO,则对每次请求内核都不阻塞,会立即返回。当没有数据的时候会返回一个错误。
3、IO复用
IO复用模型可以在等待的时候加入超时的时间,当超市时间没有达到的时候与阻塞的情况一致。而超时时间达到仍没有数据,系统会返回,不再等待。
4、信号驱动IO模型
在进程开始的时候注册一个信号处理的回调函数,进程继续执行,当信号发生时,即有了IO的时间,这里就有数据到来,利用注册的回调函数将到来的数据接收。
4、异步IO模型
异步IO与信号驱动IO类型,区别在于异步IO在数据复制完成的时候才发生信号通知注册的信号处理函数。
二、select()和pselect()
函数select()和pselsect()用于IO复用,监视多个文件描述符的集合,判断是否有符合条件的时间发生。select()和pselect()允许程序监视多个文件描述符,当一个或者多个监视的文件描述准备就绪,可以进行IO操作的时候返回。
1、select()
struct timeval
{
time_t tv_sec;//秒
long tv_usec;//微秒 1/1000000s
};
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set* readfds, fd_set* writefds,
fd_set* exceptfds, timeval* timeout);
- nfds:比所有文件描述符集合中的文件描述符的最大值大1。
- readfds:文件描述符集合,监视文件集中任何文件是否有数据可读。函数返回时,将清除其中不可读的文件描述符,只留下可读的文件描述符。
- writefds:文件描述符集合,监视文件集中任何文件是否有数据可写。函数返回时,将清除其中不可写的文件描述符,只留下可写的文件描述符。
- exceptfds:文件描述符集合,监视文件集中任何文件是否发生错误。
- timeout:设置select()所监视的文件集合中的事件没有发生时,最长的等待时间,超过此时间函数会返回。当超时时间设为NULL,表示阻塞操作会一直等待直至符合条件。当timeout为0,则会立刻返回。
select()返回值为0,-1或者大于0的整数值:当监视的文件集中有文件描述符符合要求则返回大于0的正值;超时时返回0;错误时返回-1。errno如下:
当不需要监视某种文件时,将对应的文件集设为NULL。如果所有文件集合均为NULL,则表示等待一段时间。
操作文件描述集合的宏:
- FD_ZERO():清理文件描述符集合
- FD_SET():向某个文件描述符集合中加入文件描述符
- FD_CLR():从某个文件描述符集合中取出某个文件描述符。
- FD_ISSET():测试某个文件描述符是否某个集合中的一员
文件描述符集合存在最大的限制,最大值为FD_SETSIZE。
示例:
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main( )
{
fd_set rd;//读文件集合
//监视标准输入是否可以读数据
FD_ZERO(&rd);
FD_SET(0,&rd);//
timeval tv;//时间间隔
tv.tv_sec = 5;//5s超时等待
tv.tv_usec = 0;
int err = select(1,&rd,NULL,NULL,&tv);
if (err == -1) //出错
perror("select()");
else if (err) //标准输入有数据输入,可读
printf("Data is available now.\n");//FD_ISSET(0, & rd) 的值为真
else
printf("No data within five seconds.\n");//超时,没有数据到达
return 0;
}
2、pselelct()
struct timespec
{
long tv_sec;//秒
long tv_nsec;//纳秒
};
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int pselect(int nfds,fd_set* readfds, fd_set* writefds,
fd_set* exceptfds,const timespec* timeout,const sigset_t* sigmask);
- sigmask:信号掩码
select()是用一种超时轮询的方式来查看文件的读写错误可操作性。pselect()与其类似,区别如下: - 超时的时间结构是纳秒级结构。
- 增加了进入pselect()函数时替换掉的信号处理方式,当sigmask为NULL时与select的方式一致。
- select()函数在执行后可能会改变timeout的值,改为还剩多少时间,而pselect()不会。
示例:
int child_events = 0;
void child_sig_handler(int x)//信号处理函数
{
child_events++;//调用次数+1
signal(SIGCHLD,child_sig_handler);//重设定信号回调函数
return;
}
int main()
{
//设定的信号掩码喝原始信号掩码
sigset_t sigmask, orig_sigmask;
sigemptyset(&sigmask);//清空信号
sigaddset(&sigmask,SIGCHLD);//将SIGCHLD加入sigmask
//设定信号SIG_BLOCK的掩码sigmask,并将原始掩码保存到orig_sigmask中
sigprocmask(SIG_BLOCK,&sigmask,&orig_sigmask);
//挂接对信号处理函数
signal(SIGCHLD,child_sig_handler());
//主循环
while(1)
{
for(;child_events >0;child_events--)
{
//处理动作
}
}
//pselect IO复用
int r = pselect(nfds,&fd,&wd,&er,0, &orig_sigmask);
//主程序
}
三、poll()和ppoll()
与select()完成相似的功能。
1、poll()
struct pollfd
{
int fd;//文件描述符
short events;//请求的事件
short renvent;//返回的事件
};
#include <poll.h>
int poll(pollfd* fds,nfds_t nfds,int timeout);
poll()函数监视在fds数组指明的一组文件描述符上发生的动作,当满足条件或超时的时候会退出。
- fds:指向结构pollfd数组的指针,监视的文件描述符和条件放在里面。
- nfds:比监视的最大描述符的值大1
- timeout:超时时间,单位毫秒,负值表示永远等待。
- pollfd结构中events和revents的值及含义如下:
poll()返回值:大于0表示成功,返回值为满足条件的文件描述符数量;0表示超时;-1表示发生错误,errno如下:
2、ppoll()
#include <poll.h>
int ppoll(pollfd* fds,nfds_t nfds,
const timespec* timeout,const sigset_t* sigmask);
与poll()区别:
- 超时时间采用了纳米级的变量
- 可以在ppoll函数的处理过程中挂接临时的信号掩码。
四、非阻塞编程
非阻塞方式的操作是 函数调用立刻返回,不管数据是否成功读取或成功写入。使用fcntl()将套接字文件描述符按照如下代码进行设置后,可以进行非阻塞的编程:
fcntl(fd,F_SETFL,O_NONBLOCK);
其中fd是套接字的文件描述符,使用F_SETFL命令将套接字fd设置为非阻塞方式后,再进行读写操作就可以马上返回了。
注:
LINUX网络编程 第二版 第九章 读书笔记