首页 » 技术分享 » 有限状态机的编程思想

有限状态机的编程思想

 

在C语言编程中,有限状态机是一种编程思想。在编程过程中,也可以把程序分为几个状态,比如读数据状态、写数据状态、异常状态、超时状态等,用程序实现几个状态间的互相转换。使用有限状态机的思想编程,可以使自己的程序变得更加灵活,足够应对程序编写出来后更改需求的情况。一般只要该需求,整个程序都要重新写,若用上有限状态机的思想就可以很大程度上地应对该需求的情况。在程序中,程序可以大概按下图的模式去编写:
有限状态机示意流程图

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>

#define TTY1 "/dev/tty11"//宏定义tty11文件路径
#define TTY2 "/dev/tty12"//宏定义tty12文件路径
#define BUFSIZE 10
enum//定义一个枚举,表示程序运行的各种状态
{
	STATUS_R=1,//表示读态STATUS_R=1
	STATUS_W,//表示写态STATUS_W=2
	STATUS_AUTO,//用于区分读写状态和异常停止状态
	STATUS_E,//表示异常态STATUS_E=3
	STATUS_T//表示停止态STATUS_T=4
};

typedef struct//定义一个结构体,用于文件及其状态的切换
{
	int sfd;//源文件描述符,始终从这里面读数据
	int dfd;//目标文件描述符,始终从这里面写数据
	int stat;//用于保存程序状态,其值为枚举的四个状态值
	ssize_t res;//读数据的字节大小,定义在结构体中才能有效地实现跨函数读写操作
	char buf[BUFSIZE];//读数据存放的内存空间,用于保存读取到的数据
	char *errstr;//存放错误提示文本
}stat_t;

/*relay_driver函数:负责程序的各个状态要做的事情
实现一个有限状态机*/
void relay_driver(stat_t *fms)
{
	ssize_t writeres;
	switch(fms->stat)//判断程序状态
	{
	case STATUS_R://读态
		fms->res=read(fms->sfd,fms->buf,BUFSIZE);
		if(fms->res<0)
		{
			if(errno==EAGAIN)
				fms->stat=STATUS_R;//错误为非阻塞,继续保持读数据状态,要继续读取数据
			else
			{
				fms->errstr="read()";//保存错误原因
				fms->stat=STATUS_E;//程序状态换成异常态
			}
		}
		else if(fms->res==0)
		{
			fms->stat=STATUS_R;//没有数据可读,就继续保持读态
		}
		else
			fms->stat=STATUS_W;//读取到一定字节的数据,则立刻转换为写态,向另一设备写数据
		break;
	case STATUS_W://写态
		if((writeres=write(fms->dfd,fms->buf,fms->res))<0)
		{
			if(errno==EAGAIN)
				fms->stat=STATUS_W;//错误为非阻塞,继续保持写数据状态,要继续向另一外设写数据
			else
			{
				fms->errstr="write()";//保存错误原因
				fms->stat=STATUS_E;//程序状态换成异常态
			}
		}
		if(writeres==0)
			fms->stat=STATUS_R;//没有数据可写,表示数据写完,则切换到读态去读取数据
		else
			fms->stat=STATUS_R;//已经写入一定字节的数据,则切换到读态去读取数据
		break;
	case STATUS_E://异常态
		perror(fms->errstr);//报告异常错误原因
		fms->stat=STATUS_T;//出现异常,则切换到停止态
		break;
	case STATUS_T://停止态
		break;
	}
}
/*max函数:比较两个数的大小,用于select函数*/
static int max(int a,int b)
{
	return a>b?a:b;
}

void relay(int fd1,int fd2)
{
	int save_fd1,save_fd2;//保存两个文件被改变前的状态
	stat_t fms12,fms21;//两个外部设备
	char buf1[32]="----------TTY11----------\n";
	char buf2[32]="----------TTY12----------\n";
	fd_set rfds,wfds;

	save_fd1=fcntl(fd1,F_GETFL);//获取文件描述符对应文件被打开的状态
	save_fd2=fcntl(fd2,F_GETFL);
	fcntl(fd1,F_SETFL,save_fd1|O_NONBLOCK);//设置追加文件为非阻塞
	fcntl(fd2,F_SETFL,save_fd2|O_NONBLOCK);

	write(fd1,buf1,sizeof(buf1));
	write(fd2,buf2,sizeof(buf2));
/*初始化两个外设,确定两个文件的源文件和目标文件,确定两个文件的初始化状态*/
	fms12.sfd=fd1;
	fms12.dfd=fd2;
	fms12.stat=STATUS_R;

	fms21.sfd=fd2;
	fms21.dfd=fd1;
	fms21.stat=STATUS_R;
/*只要两个文件的状态不是停止态,就一直循环(轮询)*/
	while(fms12.stat!=STATUS_T||fms21.stat!=STATUS_T)
	{
		FD_ZERO(&rfds);//首先清空读集合
		FD_ZERO(&wfds);//清空写集合
/*根据状态决定相应的文件描述符加入到哪一个集合。
对于即将要向某个文件描述符写数据,
那就将这个文件描述符加入到写集合中,其余的同理*/
		if(fms12.stat==STATUS_R)
			FD_SET(fms12.sfd,&rfds);
		if(fms12.stat==STATUS_W)
			FD_SET(fms12.dfd,&wfds);
		if(fms21.stat==STATUS_R)
			FD_SET(fms21.sfd,&rfds);
		if(fms21.stat==STATUS_W)
			FD_SET(fms21.dfd,&wfds);

		if(select(max(fd1,fd2)+1,&rfds,&wfds,NULL,NULL)<0)
		{
			if(errno==EINTR)
				continue;//如果被信号打断,只能重新初始化集合
			perror("select()");
			exit(1);
		}

		if(FD_ISSET(fms12.sfd,&rfds)||FD_ISSET(fms12.dfd,&wfds)||fms12.stat>STATUS_AUTO)
			relay_driver(&fms12);
		if(FD_ISSET(fms21.sfd,&rfds)||FD_ISSET(fms21.dfd,&wfds)||fms21.stat>STATUS_AUTO)
			relay_driver(&fms21);
	}

	fcntl(fd1,F_SETFL,save_fd1);//最后要还原文件进入当前函数时的状态
	fcntl(fd2,F_SETFL,save_fd2);
}

int main(void)
{
	int fd1,fd2;

	fd1=open(TTY1,O_RDWR|O_NONBLOCK);
	if(fd1<0)
	{
		perror("open()");
		exit(1);
	}

	fd2=open(TTY2,O_RDWR|O_NONBLOCK);
	if(fd2<0)
	{
		perror("open()");
		exit(1);
	}

	relay(fd1,fd2);

	close(fd1);
	close(fd2);
	exit(0);
}

转载自原文链接, 如需删除请联系管理员。

原文链接:有限状态机的编程思想,转载请注明来源!

0