协议栈 状态机 java_状态机技术在数据通讯协议栈中的编程应用

摘 要:在数据通讯软件中,上位机(PC机)数据通讯协议栈的编写工作通常由Switch/Case分支语句来完成。分支语句是一种简单初级的逻辑表达式,因此不易做到模块化,扩充性能和可移植性能都不理想,因而引入了状态机技术。通过将通讯协议栈抽象为有序的不同状态和行为,再嵌入到C++类中,可模拟状态机实现此通讯处理,在Visual C++6.0平台上运行可靠,稳定性好,并且状态行为具有可继承性。

关键词:FSM;数据采集;协议栈;分支语句

中图分类号:TP311.52 文献标识码:B

文章编号:1004373X(2008)0314603

FSM Methodology and Its Application in Communication Protocol Stack

ZHANG Laming,TONG Yu

(School of Mechanical & Engineering,Dalian University of Technology,Dalian,116024,China)

Abstract:Switch/Case syntax is the traditional method to program the communication protocol stack in the software of data communication.However,Switch/Case syntax as a simple logical expression neither have expansibility and transplantable performance nor can be modularization.A new method based on FSM methodology is introduced in this paper.Dividing communication protocol stack into a lot of states and actions,which is embedded into the C++ class and can be inherited by other classes,making the software work reliably and stability on Visual C++ platform.

Keywords:FSM;data acquisition;protocol stack;syntax

在计算机数字通讯中经常要对通讯数据进行打包、解包和校验等多种顺序操作,相应的也要求协议软件能对接收的数据依次进行相关处理,从而表现出数字通讯中的多阶段性特征,为了满足分阶段处理通讯数据的需要,引进了状态机技术。由于状态机能以模块化支撑协议,因而在数据通讯协议栈处理技术方面扮演着越来越重要的角色。

1 状态机简介

状态机(有限状态机)是一种具有离散输入输出系统的模型。任何时刻他都处于一个特定的状态,状态的转换依赖于系统所接受的事件。当在某状态下有事件发生时,系统会根据输入的事件和当前的状态做出反映,从而决定如何处理该事件以及是否转换到下一状态[1]。

状态的触发事件通常由外部信号来完成,当有效的触发事件发生时,便进入下一状态(当然也可以不发生状态转移),同时完成本状态的具体任务,直到所有状态完成,再回到初始状态。当某一状态出现异常时,也返回初始状态,等待下一触发事件的出现,如此反复循环。

状态机通常有两种表达方式:状态表和状态图。

在图1所示简单状态图中,当状态机处于状态1时,完成动作1;事件1可以触发状态机跳转到状态2,以执行动作2;同样,事件2也可以触发状态2的转移,从而又回到状态1。图1所示状态和转移都很简单,真正的状态机比他要复杂的多。

图1 状态事件图

对应图1的状态表如表1所示。其中,括号内为当前状态动作和下一状态,空网格表示在此状态下,此事件无效,即不能触发状态的转移,当然也不执行任何动作。

表1状态事件表

2 状态机的实现

在高级编程语言(如C,C++)中状态机的典型实现主要有:

嵌套的Switch语句;

状态表;

面向对象状态设计模式。

其他技术几乎是前面3种方式的组合[2]。

Switch/Case语句是一种内联性很强、病态耦合的编程技术,是一种简单初级的逻辑表达式,因此不易做到模块化、灵活的可扩充性和可移植性,至于鲁棒性就更差了。现代软件技术讲究松耦合、可移植、可快速扩充,并要求安全可靠,而状态表的实现正是基于这一思想而发展的。当要在状态机中增加新的状态与控制逻辑,只需在状态表中修改即可,甚至可以动态修改,C++的指针完美地支持这一点。在状态变化不是很复杂的情况下,这是一种非常可取的方法。

3 数据采集系统PC端数据通讯协议栈的状态机实现

在笔者所编写的土壤渗流实验数据采集系统PC端软件中,很好的利用了状态机技术来完成数据通讯协议栈的处理。此实验数据的采集由下位机(单片机)和上位机(PC机)共同来完成。对于PC端软件来说,正确的数据接收是后续工作的前提和保障。因此,有必要对接收到的每一帧消息进行检测和校验,即进行数据的预处理。只有符合通讯协议的数据才进行后续处理,不符合通讯协议的数据将丢弃掉,并且通知下位机数据出错。下面将结合笔者参与开发的数据采集系统,论述状态机技术在PC端软件中的编程应用:

3.1 上下位机软件的串行通讯协议

3.1.1 PDU协议

PDU数据单元分为两类:指令类PDU和数据类PDU。指令PDU用于在通讯设备之间传输控制信息和状态信息;数据PDU用于在通讯设备之间传输采集数据。本协议PDU格式如下:

功能代码子功能代码(可选)参数数据

3.1.2 物理层(PHY)

物理层接口的连接部件为D型9针连接器,其电平定义为标准RS 232电平标准,上下位机通过RS 232串口实现通讯。

3.1.3 逻辑链路子层(LLC)

逻辑链路子层的主要工作就是提供帧处理服务与差错控制服务。

帧处理服务分为两种级别――数据帧服务和消息帧服务。数据帧服务控制数据帧的比特数,停止位数;消息帧服务提供消息帧头和消息帧尾来封装消息帧,以实现发送与接收的消息同步,正确界定消息帧。在本通讯协议中采用消息帧,用ASCII字符“:”标记消息帧头,以连续的ASCII字符CR+LR标记消息帧尾。

LLC层还提供消息帧差错控制服务:在本协议中采用LRC校验算法。通讯时,发送方按LRC算法生成LRC校验码,按照高位在前低位在后的顺序附加在原始的消息帧尾部,最后以“:”和CR+LF对消息进行封装。接收方按LRC算法将接收到的LRC校验码与在本地运算得到的校验码进行对比,从而判断接收到的数据的正确性。

3.1.4 ADU格式

上位机(PC机)与下位机通讯协议的ADU格式如下:

3.2 状态表的制定

按照本协议,上位机对接收到的每一帧消息进行校验。其中功能代码,数据,LRC码在一帧消息中处于一定的先后位置,有相应的动作(如保存,校核等)与之对应,可以视为不同的状态,另外,消息帧头、帧尾等也视为不同的状态。而对于每一个接收的字符,可以视其为触发信号。如当接收的数据帧中出现数字时,视其为DATA_SIG信号,当接收的数据帧中出现回车(CR)字符时,视其为CR_SIG信号等,从而通过不断接收字符来触发状态的行为和转移,使状态机循环工作下去。在分析数据通讯协议的基础上,通过抽象,可将每帧消息分为如表2所示几个状态及触发信号。

在表2中顶行列出了信号(触发或事件),最左边一列是状态。各网格的内容是转换,他表示为{动作,下一状态}。例如,在BEGIN状态中,当接收到COLON_SIG信号时,将会调用DoClear()这一函数,从而完成一些数据复位工作,同时,也触发状态到下一状态――MAINCODE状态。而对于其他触发信号,状态机状态不变,仍在BEGIN状态,且什么动作也不做。COLON_SIG信号对应数据帧的“:”字符,即当接收到“:”字符后,就向状态机发出一COLON_SIG信号,使状态机开始运行。而对接收的其他字符,在没有收到“:”字符之前,协议要求不做处理。表中的其他动作解释如下:

RecordType() //记录功能代码,指令类代码或数据类代码

DataAdd()//数据记录

DoLRC() //完成LRC校验工作

DoFinish()//数据检验完毕处理

ComRecord()//记录子功能代码

RecordState()//记录数据通道状态(正常或不正常)

DoError()//数据帧出错报告

3.3 状态表的实现

上位机软件用Visual C++ 6.0 编写,C++语言基于面向对象编程(OOP)思想,而状态机的核心机制是行为继承,这与OOP模式很相似,超状态的行为能很容易地被子状态所继承。鉴于此,可以利用C++的类来表现状态行为,即将状态行为嵌入到C++类中,从而在Visual C++平台上可以很好地运行状态机。本状态表的实现就是利用这一机制,父类封装了抽象的状态转换和当前状态行为,子类实现具体的状态行为和状态转换。详见如下核心代码。

表2 数据通讯协议栈处理状态表

父类StatusClass 类核心代码如下:

// 定义成员函数指针

typedef void (StatusClass::* Action) ();

// 定义内层结构转换

struct Translate

{

Action action; // 抽象行为动作

unsigned nextStatus; // 抽象的下一动作

};

// 状态行为和转换

void StatusClass::dispatch(unsigned const sig)

{

register Translateconst *t=myTable+ myState* myNsignals+sig;// 查状态表

(this->*(t->action)) ();

myState=t->nextStatus;

}

// 无状态行为的缺省动作

Void StatusClass::doNothing()

{

……

}

子类SubStatusClass 类核心代码如下:

// 定义的信号

enum

Event{OTHER_SIG,C_SIG,D_SIG,DATA_SIG,CHAR_SIG,CR_SIG,LF_SIG,COLON_SIG,MAX_SIG};

//定义的状态

enum

State{BEGIN,MAINCODE,DATA,SUBCODE,PARAM,MAX_STATE};

// 初始化状态机

void SubStatusClass::init()

{

……

}

// 状态表,包含实际的状态行为和状态转换

(此状态表对应于表2所示状态和行为)

StatusClass::Tran const SubStatusClassClass::myTable[MAX_STATE][MAX_SIG]= {

{{&StatusClass::doNothing,BEGIN},{&StatusClass::doNothing,BEGIN},{&StatusClass::doNothing,BEGIN},{&StatusClass::doNothing,BEGIN},

{&StatusClass::doNothing,BEGIN},{&StatusClass::doNothing,BEGIN},{&StatusClass::doNothing,BEGIN},

{static_cast(&SubStatusClassClass::DoClear),MAINCODE}},

……

};

3.4 状态类的调用

笔者已经将状态行为封装至C++类当中,所以在程序中调用时只须定义一个StatusClass类的对象即可运行状态机了。同样,随着对象的销毁,状态机的生命周期也就结束。在本软件中,数据的通讯协议栈处理是在单独一个线程(预处理线程)中完成的,而此线程的触发依赖于数据的接收。故当有数据帧接收时便会使预处理线程恢复,从而启动状态机,实现对数据帧的校验。状态类调用核心代码如下:

DWORD WINAPI

CMycomFirDlg::ProtocalThread(LPVOID lpParameter )

{

……

// 创建状态类对象

SubStatusClassClassmyStatus;

// 初始化状态机

myStatus.init();

……

// 状态机入口

myStatus.DealChar()

……

// 挂起预处理线程

SuspendThread(Protocal_handle);

}

4 结 语

实践证明,将状态机技术巧妙地运用于数据通讯协议栈处理程序中,收到了很好的效果,不但可以避免大量的Switch/Case语句,使程序简洁,而且提高了程序的扩展性能,便于维护和修改。同时,由于状态机的行为嵌入到了C++的类当中,可以视其为一个类,因而具有类的所有特征,可以被继承,移植性能好,具有实用价值。

参考文献

[1]魏先民.有限状态机在嵌入式软件中的应用[J].潍坊学报,2007,6(4):24―25.

[2]Samek Mico.Practical Statecharts in C/C++――Quantem Programming for Embedded Systems[M].USA:CMP Books (CMP Media LLC),2002.

作者简介 张腊明 男,1982年出生,湖南长沙人,大连理工大学机械学院硕士研究生。主要研究方向为机电控制。

注:本文中所涉及到的图表、注解、公式等内容请以PDF格式阅读原文。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值