1.非阻塞IO和阻塞IO
简单流程:自然流程是结构化的
复杂流程:自然流程不是结构化的。可以通过有限状态机的编程方式实现程序易拓展性;
1.1 fcntl()函数
用途:
1)对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性,比如文件是否阻塞;
2)
#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);
参数:
fd:
文件描述符
cmd:
设置的命令
F_GETFL(常用)
F_SETFL(常用)
arg:
可有可无,由第二个参数决定,比如get时候没有,set时候有值
返回值:
文件状态标志
-1 :失败
问题:
1.对于测试现场复原不了,需要解决
2.读、写操作都是空闲时忙等,浪费cpu资源
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define TTY1 "/dev/tty2"
#define TTY2 "/dev/tty5"
#define BUFSIZE 1024
enum
{
STATE_R = 1, // read
STATE_W, // write
STATE_EX, // error
STATE_T // over
};
struct fsm_st
{
int state;
int sfd;
int dfd;
char buf[BUFSIZE];
int len;
int pos;
char *errstr;
};
static void fsm_driver(struct fsm_st *fsm)
{
int ret;
switch (fsm->state)
{
case STATE_R:
fsm->len = read(fsm->sfd, fsm->buf, BUFSIZE);
if(fsm->len == 0)
fsm->state = STATE_T;
else if(fsm->len < 0)
{
if(errno == EAGAIN)
fsm->state = STATE_R;
else
{
fsm->errstr = "read()";
fsm->state = STATE_EX;
}
}
else
{
fsm->pos = 0;
fsm->state = STATE_W;
}
break;
case STATE_W:
ret = write(fsm->dfd, fsm->buf + fsm->pos, fsm->len);
if(ret < 0)
{
if(errno == EAGAIN)
fsm->state = STATE_W;
else
{
fsm->errstr = "write()";
fsm->state = STATE_EX;
}
}
else
{
fsm->pos += ret;
fsm->len -= ret;
if(fsm->len == 0)
fsm->state = STATE_R;
else
fsm->state = STATE_W;
}
break;
case STATE_EX:
perror(fsm->errstr);
fsm->state = STATE_T;
break;
case STATE_T:
break;
default:
abort();
break;
}
}
static void relay(int fd1, int fd2)
{
int fd1_save, fd2_save;
struct fsm_st fsm12, fsm21;
fd1_save = fcntl(fd1, F_GETFL);
fcntl(fd1, F_SETFL, fd1_save|O_NONBLOCK);
fd2_save = fcntl(fd2, F_GETFL);
fcntl(fd2, F_SETFL, fd2_save|O_NONBLOCK);
fsm12.state = STATE_R;
fsm12.sfd = fd1;
fsm12.dfd = fd2;
fsm21.state = STATE_R;
fsm21.sfd = fd2;
fsm21.dfd = fd1;
while(fsm12.state != STATE_T || fsm21.state != STATE_T)
{
fsm_driver(&fsm12);
fsm_driver(&fsm21);
}
fcntl(fd1, F_SETFL, fd1_save);
fcntl(fd2, F_SETFL, fd2_save);
}
int main()
{
int fd1, fd2;
fd1 = open(TTY1, O_RDWR);
if(fd1 < 0)
{
perror("open()");
exit(1);
}
write(fd1, "TTY1\n", 5);
fd2 = open(TTY2, O_RDWR|O_NONBLOCK);
if(fd2 < 0)
{
perror("open()");
exit(1);
}
write(fd2, "TTY2\n", 5);
relay(fd1, fd2);
close(fd2);
close(fd1);
exit(0);
}
2.IO多路转接
可以监控多个文件描述符,把读、写空闲时间的忙等屏蔽掉。
2.1 select()函数
特点:
以事件为单位,组织文件描述符
#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, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
参数:
nfds:
一个整数值, 表示集合中所有文件描述符的范围,即所有文件描述符中的最大值+1;
linux select第一个参数的函数: 待测试的描述集的总个数。 但要注意, 待测试的描述集总是从0, 1, 2, …开始的。 所以, 假如你要检测的描述符为8, 9, 10, 那么系统实际也要监测0, 1, 2, 3, 4, 5, 6, 7, 此时真正待测试的描述符的个数为11个, 也就是max(8, 9, 10) + 1
注意:
1、如果你要检测描述符8, 9, 10, 但是你把select的第一个参数定为8, 实际上只检测0到7, 所以select不会感知到8, 9, 10描述符的变化。
2、入果你要检测描述符8, 9, 10, 且你把select的第一个参数定为11, 实际上会检测0-10, 但是, 如果你不把描述如0 set到描述符中, 那么select也不会感知到0描述符的变化。
所以, select感知到描述符变化的必要条件是, 第一个参数要合理, 比如定义为fdmax+1, 且把需要检测的描述符set到描述集中。
readfds:
监视文件描述符的一个集合,我们监视其中的文件描述符是不是可读,或者更准确的说,读取是不是不阻塞了。
writefds:
监视文件描述符的一个集合,我们监视其中的文件描述符是不是可写,或者更准确的说,写入是不是不阻塞了。
exceptfds:
用来监视发生错误异常文件
timeout:
struct timeval{
long tv_sec;//秒
long tv_usec;//微秒
}
timeout表示select返回之前的时间上限。
如果timeout==NULL,无期限等待下去,这个等待可以被一个信号中断(假错),只有当一个描述符准备好,或者捕获到一个信号时函数才会返回。如果是捕获到信号,select返回-1,并将变量errno设置成EINTR。
如果timeout->tv_sec0 && timeout->tv_sec0 ,不等待直接返回,加入的描述符都会被测试,并且返回满足要求的描述符个数,这种方法通过轮询,无阻塞地获得了多个文件描述符状态。
如果timeout->tv_sec!=0 || timeout->tv_sec!=0 ,等待指定的时间。当有描述符复合条件或者超过超时时间的话,函数返回。等待总是会被信号中断
返回值
成功时:返回三中描述符集合中”准备好了“的文件描述符数量。
超时:返回0
错误:返回-1,并设置 errno
EBADF:集合中包含无效的文件描述符。(文件描述符已经关闭了,或者文件描述符上已经有错误了)。
EINTR:捕获到一个信号。
EINVAL:nfds是负的或者timeout中包含的值无效。
ENOMEM:无法为内部表分配内存。
fd_set
一个文件描述符集合保存在fd_set变量中,可读,可写,异常这三个描述符集合需要使用三个变量来保存,分别是 readfds,writefds,exceptfds。我们可以认为一个fd_set变量是由很多个二进制构成的数组,每一位表示一个文件描述符是否需要监视。
对于fd_set类型的变量,我们只能使用相关的函数来操作。
缺陷:
1)fd_set没有const修饰,监视现场和监视结果存放的是同一块地址空间。所以不能用while,会绕圈
2)nfds为int,有最大值限制
3)监测时间太单一,除了读(readfds)、写(writefds),其他的都算作异常
void FD_CLR(int fd, fd_set *set);//清除某一个被监视的文件描述符。
int FD_ISSET(int fd, fd_set *set);//检查一个文件描述符是否是集合中的一员
void FD_SET(int fd, fd_set *set);//添加一个文件描述符,将set中的某一位设置成1;
void FD_ZERO(fd_set *set);//清空集合中的文件描述符,将每一位都设置为0;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/select.h>
#define TTY1 "/dev/tty2"
#define TTY2 "/dev/tty5"
#define BUFSIZE 1024
enum
{
STATE_R = 1, // read
STATE_W, // write
STATE_AUTO,
STATE_EX, // error
STATE_T // over
};
struct fsm_st
{
int state;
int sfd;
int dfd;
char buf[BUFSIZE];
int len;
int pos;
char *errstr;
};
static void fsm_driver(struct fsm_st *fsm)
{
int ret;
switch (fsm->state)
{
case STATE_R:
fsm->len = read(fsm->sfd, fsm->buf, BUFSIZE);
if(fsm->len == 0)
fsm->state = STATE_T;
else if(fsm->len < 0)
{
if(errno == EAGAIN)
fsm->state = STATE_R;
else
{
fsm->errstr = "read()";
fsm->state = STATE_EX;
}
}
else
{
fsm->pos = 0;
fsm->state = STATE_W;
}
break;
case STATE_W:
ret = write(fsm->dfd, fsm->buf + fsm->pos, fsm->len);
if(ret < 0)
{
if(errno == EAGAIN)
fsm->state = STATE_W;
else
{
fsm->errstr = "write()";
fsm->state = STATE_EX;
}
}
else
{
fsm->pos += ret;
fsm->len -= ret;
if(fsm->len == 0)
fsm->state = STATE_R;
else
fsm->state = STATE_W;
}
break;
case STATE_EX:
perror(fsm->errstr);
fsm->state = STATE_T;
break;
case STATE_T:
break;
default:
abort();
break;
}
}
static int max(int a, int b)
{
if(a >= b)
return a;
return b;
}
static void relay(int fd1, int fd2)
{
int fd1_save, fd2_save;
struct fsm_st fsm12, fsm21;
fd_set rset, wset;
fd1_save = fcntl(fd1, F_GETFL);
fcntl(fd1, F_SETFL, fd1_save|O_NONBLOCK);
fd2_save = fcntl(fd2, F_GETFL);
fcntl(fd2, F_SETFL, fd2_save|O_NONBLOCK);
fsm12.state = STATE_R;
fsm12.sfd = fd1;
fsm12.dfd = fd2;
fsm21.state = STATE_R;
fsm21.sfd = fd2;
fsm21.dfd = fd1;
while(fsm12.state != STATE_T || fsm21.state != STATE_T)
{
//布置监视任务
FD_ZERO(&rset);
FD_ZERO(&wset);
if(fsm12.state == STATE_R)
FD_SET(fsm12.sfd, &rset);
if(fsm12.state == STATE_W)
FD_SET(fsm12.dfd, &wset);
if(fsm21.state == STATE_R)
FD_SET(fsm21.sfd, &rset);
if(fsm21.state == STATE_W)
FD_SET(fsm21.dfd, &wset);
//监视,不能应用while,continue后只能再次调用监视任务
//如果到STATE_EX或者STATE_T态,需要在重新进入
if(fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO)
{
if(select(max(fd1, fd2) + 1, &rset, &wset, NULL, NULL) < 0)
{
if(EINTR == errno)
continue;
perror("selcet()");
exit(1);
}
}
/*
查看监控结果
fsm12.state > STATE_AUTO: 如果状态机状态为EX或T,则需要把状态无条件推动
*/
if(FD_ISSET(fd1, &rset) || FD_ISSET(fd2, &wset) || fsm12.state > STATE_AUTO)
{
fsm_driver(&fsm12);
}
if(FD_ISSET(fd2, &rset) || FD_ISSET(fd1, &wset) || fsm21.state > STATE_AUTO)
{
fsm_driver(&fsm21);
}
}
fcntl(fd1, F_SETFL, fd1_save);
fcntl(fd2, F_SETFL, fd2_save);
}
int main()
{
int fd1, fd2;
fd1 = open(TTY1, O_RDWR);
if(fd1 < 0)
{
perror("open()");
exit(1);
}
write(fd1, "TTY1\n", 5);
fd2 = open(TTY2, O_RDWR|O_NONBLOCK);
if(fd2 < 0)
{
perror("open()");
exit(1);
}
write(fd2, "TTY2\n", 5);
relay(fd1, fd2);
close(fd2);
close(fd1);
exit(0);
}
2.2 poll()函数
特点:
以文件描述符为单位,组织事件
2.3 epoll()函数
从poll的基础上,linux自己做的监控文件描述符的版本
相比于poll,不可移植;