高级IO
1 非阻塞IO/有限状态机编程
1.1 基本概念
定义
有限状态机(Finite State Machine) 缩写为 FSM,状态机有 3 个组成部分:状态、事件、动作。
- 状态:所有可能存在的状态。包括当前状态和条件满足后要迁移的状态。
- 事件:也称为转移条件,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
- 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是* 必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
作用
用来解决复杂流程的问题:
- 简单流程:一个程序的自然流程是结构化
- 复杂流程:一个程序的自然流程不是结构化的
自然流程即解决问题的直接思路
1.2 有限状态机实现文件复制
分析
实现的功能就是从文件1中复制文件到文件2或者从文件2中复制文件到文件1,有限状态机共有4个状态,分别是read读态,write写态,exception出错态和terminate结束态。
FSM模型图如下:
实现
#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
#define BUFSIZE 1024
enum {
STATE_R = 1, //读态
STATE_W = 2, //写态
STATE_EX = 3, //异常态
STATE_T = 4 //退出态
};
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->state < 0) {
if (errno == EAGAIN) fsm->state = STATE_R; // 假错接着读
else {
fsm->state = STATE_EX; // 真错切换到异常态
fsm->errstr = "read()";
}
} 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 (ret == 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();
}
}
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 argc, char **argv) {
// 模拟用户打开两个设备的操作
int fd1, fd2;
fd1 = open(TTY1, O_RDWR);
if (fd1 < 0) {
perror("open()");
exit(1);
}
fd2 = open(TTY2, O_RDWR | O_NONBLOCK);
if (fd2 < 0) {
close(fd1);
perror("open()");
exit(1);
}
// 调用数据中继函数
relay(fd1, fd2);
close(fd1);
close(fd2);
exit(0);
}
1.3 中继引擎
头文件
#ifndef LINUX_RELAYER_H
#define LINUX_RELAYER_H
#include <stdint-gcc.h>
#include <time.h>
#define REL_JOBMAX 10000
enum {
STATE_RUNNING = 1,
STATE_CANCELED,
STATE_OVER
};
/**
* 表示任务状态的结构体,这个结构体是我们希望用户看到的,当我们实际操作的时候可以不是这个结构体
* 即实现结构体部分的隐藏和封装
*/
struct rel_stat_st {
int state;
int fd1;
int fd2;
int64_t count12, count21; //通讯的字节数
time_t start, end; //记录任务的起始时间
};
/**
* 添加任务
* @param fd1
* @param fd2
* @return >=0 表示成功,返回当前任务id;-EINVAL 表示失败参数非法; -ENOSPC 表示失败,任务数组满
* -ENOMEM 表示失败,内存分配有误
*/
int rel_addjob(int fd1, int fd2);
/**
* 取消任务
* @param id
* @return =0 表示指定任务已经成功取;-EINVAL 表示失败,参数非法; -EBUSY 失败,任务被重复取消
*/
int rel_canceljob(int id);
/**
* 给任务收尸
* @param id 任务id
* @param old 被收尸任务的状态
* @return =0 成功,指定任务已终止并返回状态; -EINVAL 失败,参数非法
*/
int rel_waitjob(int id, struct rel_stat_st *old);
/**
* 返回id任务的状态
* @param id 任务id
* @param stat 任务状态
* @return 0 成功,指定任务状态已返回; -EINVAL 失败,参数非法
*/
int rel_statjob(int id, struct rel_stat_st *stat);
#endif //LINUX_RELAYER_H
实现
#include <malloc.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "relayer.h"
#define