非阻塞有限状态机的实现 C语言
非阻塞IO – 阻塞IO
IO多路转接
其他读写函数
存储映射IO
文件锁
非阻塞 IO
简单流程:如果一个程序的自然流程是结构化的,就是简单流程。(明确能分析结构流程的)
复杂流程:如果一个程序的自然流程不是结构化的,就是复杂流程。
有限状态机的实现方法:
任务:实现一个数据中继模型。(流式套接字)
- - -> 设备1 <——> 设备2 < - - -
目标:
a. 读设备1——写设备2——读设备2——写设备1
如果采用阻塞的方式,假如正在读设备1,且设备1上没有数据,程序就会一直阻塞在读设备1;假设设备2有数据产生,就会无法读设备2并像设备1中写入。这种方案的阻塞是无法实现功能的。
如果采用非阻塞方式,假如在读取某一个设备时没有数据,就会去尝试读取另一个设备,就不会产生上述问题。
a.读设备1——写设备2
b.读设备2——写设备1
这种采用两个线程分工,如果采用阻塞的方式是可以实现功能,但是效果很差。
这种采用两个线程分工,如果采用非阻塞的方式是可以实现功能。
原型:
- 读dev1写dev2
- 读dev2写dev1
状态机:
实现
简单实现:relay.c
/******************************************************************************
* @file relay.c
* @brief 非阻塞有限状态机数据中继简单实现,可以通过状态机实现两个终端的数据同步
* @author wangs7__
* @date 2020/10/05
*
*******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define TTY1 "/dev/tty11" //设备1
#define TTY2 "/dev/tty12" //设备2
#define BUFSIZE 1024 //缓冲区大小
//状态图
enum{
STATE_R = 1, //写状态
STATE_W , //读状态
STATE_Ex, //出错状态
STATE_T //退出态
};
//状态机结构体
struct fsm_st {
int state; //状态描述变量
int sfd; //源文件描述符
int dfd; //目标文件描述符
int len; //读取的字节数
int pos; //写指针
char buf[BUFSIZE]; //读写缓冲区
char *errstr; //报错提示
};
/**
* @name fsm_driver
* @brief 状态机驱动函数,推动状态机运转
* @param fsm 状态机结构体指针
* @return void
*/
static void fsm_driver(struct fsm_st *fsm){
int ret; //写函数返回值
//开关选择语句 状态机分支
switch (fsm->state)
{
//R态-------------------------------------------------------------------
case STATE_R:
fsm->len = read(fsm->sfd, fsm->buf, BUFSIZE);//从源文件读取字符
if(fsm->len == 0){ //读取文件为空 转T态
fsm->state = STATE_T;
}
else if (fsm->len < 0){ //读取失败
if(errno == EAGAIN){ //假错 保持R态
fsm->state = STATE_R;
}
else{ //真错 转Ex态
fsm->errstr = "read()";
fsm->state = STATE_Ex;
}
}
else{ //读成功 转W态
fsm->pos = 0; //写指针初始化
fsm->state = STATE_W;
}
break;
//W态-------------------------------------------------------------------
case STATE_W:
ret = write(fsm->dfd, fsm->buf+fsm->pos, fsm->len);//从缓冲区向目标文件写入
if(ret < 0){ //写错误
if(errno == EAGAIN){ //假错 保持写
fsm->state = STATE_W;
}
else{ //真错 转Ex态
fsm->errstr = "write()";
fsm->state = STATE_Ex;
}
}
else{ //写成功
fsm->len -= ret; //写剩余长度
fsm->pos += ret; //写指针位置更新
if(fsm->len == 0){ //写完 转读态
fsm->state = STATE_R;
}
else{ //未写完 保持写
fsm->state = STATE_W;
}
}
break;
//Ex态------------------------------------------------------------------
case STATE_Ex:
perror(fsm->errstr); //报错提示
fsm->state = STATE_T; //转T态
break;
//T态-------------------------------------------------------------------
case STATE_T:
/* do something */
break;
//---------------------------------------------------------------------
default:
abort();
break;
}
}
/**
* @name relay
* @brief 数据中继函数
* @param fd1 设备1文件描述符
* @param fd2 设备2文件描述符
* @return void
*/
void relay(int fd1, int fd2){
int fd1_save, fd2_save; //原始文件打开状态
struct fsm_st fsm12, fsm21; //1->>2状态机 和 2->1状态机
//添加非阻塞打开方式
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);//驱动 1->2
fsm_driver(&fsm21);//驱动 2->1
}
//回复文件原始打开状态
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);
/* 如果出错 */
write(fd1, "TTY1\n", 5);
fd2 = open(TTY2, O_RDWR | O_NONBLOCK);
/* 如果出错 */
write(fd2, "TTY2\n", 5);
//数据中继驱动函数
relay(fd1, fd2);
close(fd1);
close(fd2);
exit(0);
}