一个带心跳检测的通用TCP-IP通讯类的设计和实现
The Design and Implement of a Universal TCP/IP Communication Class with Heart-Beat Checking
CSSRC.IT.LIPO Team
应用领域:
TCP/IP通讯
使用的产品:
NI开发者套件2006(LabVIEW8.2)
挑战:
利用LabVIEW8.2创建一个带心跳检测的通用TCP/IP通讯类,该类的运用可带来功能模块复用和扩展上的极大便利。
应用方案:
充分利用LabVIEW8.2最新面向对象(Object-Oriented)编程方式的特性――类和对象(lvclass),创建一个带心跳检测的TCP/IP通讯的类(TcpClient.lvclass),实现TCP/IP通讯的创建、保持、消息收发、关闭等底层操作的封装,同时给予类的使用者一些简单的函数接口。另外利用自定义事件等功能实现操作的异步调用。
1 背景介绍
带心跳检测的TCP/IP通讯的通用类模块的功能需求来源于中国船舶科学研究中心的大型深水拖曳水池试验系统开发项目。作为“十一·五”数字化示范工程的重要组成部分之一,该项目希望实现实验室数字化建设中若干重大的创新性要点,为中国船舶科学研究中心实验室数字化的发展方向树立示范典型。
在整个系统众多的创新性设计目标中,有一点是实现试验仪器的自动识别。为了实现这个目标,设计了如下的系统架构。
图 1‑1 仪器自动识别设计系统图
系统架构包括3大部分:仪器智能化系统、试验采集系统、设备信息数据库。
其中仪器智能化系统的功能是给试验设备添加唯一标识以完成设备的智能化,并通过一个基于WinCE的网关监视接入自研开发的智能仪器识别总线上的智能化设备。并在试验采集系统征询时上报连接到系统的所有试验设备标识信息。
图 1‑2 仪器智能化系统部分
试验采集系统是整个系统的核心,一方面它通过TCP/IP以太网和WinCE网关连接。在试验开始前通过向网关的征询获得所有试验设备的标识信息;另外一方面,通过无线网络连接一个存储所有设备信息的Oracle数据库,根据设备标识就可查询到试验设备的相关信息,从而实现试验仪器的自动识别。
为了实现试验采集系统和WinCE网关的TCP/IP通讯,设计并实现一个类模块是非常有意义的。
2 需求分析
2.1 通讯消息方式
为实现试验采集系统和WinCE网关之间的局域网通讯,我们需要设计专门的TCP/IP高层应用协议。
正如LabVIEW帮助中提到的(TCP Writer.vi),在进行变长度消息通讯中,常用的有效方式有以下三种:
1) 发送端每次发送使用结构:消息体长度+消息体内容。读取端读取时采用两次读取的方式,先读取固定字节(消息长度值),再根据内容解析得到消息体长度,读取相应大小的消息体内容;
2) 接收和发送使用固定长度字节。如果消息体长度小于设定长度,使用特殊标记填充补齐;如果消息体长度超过固定长度,则使用特殊标记续接;
3) 使用ASCII作为消息体内容,同时每段消息以回车换行(CR LF)结束。
以上三种方式在实际中应用都非常广泛。方式 1) 非常灵活,只是每次需要两次读写,方式 2) 在不需要考虑补齐和续接时发送和读取效率最高,方式 3) 较为复杂,但在很多 Internet 协议中运用,如 POP3, FTP, HTTP 等等。
最终我们采用的是第三种方式,主要基于以下原因: 本文中论述的TCP/IP类的设计正是基于ASCII传输的这种方式,完全实现了4)中描述的底层接收发送。这样的分层分离带来了良好的通用性和扩展性。
1) 试验采集系统和WinCE网关通讯的数据量比较小,效率;
2) 避免LabVIEW和嵌入式系统对整型、浮动等变量的字节长度定义不一致;
3) ASCII明码的方式易于理解和扩展;
4) 接收到的消息都是字符串,从而可以将消息的高层解析和底层接收发送处理进行分离,实现分层处理;
5) LabVIEW的TCP读取模式包含对此方式的直接支持。
2.2 通讯时连接状态的保持和检测
由于以太网本身的不可靠性,客户端和服务器通讯连接的断开是难以即时发现。即使TCP协议通过超时重传、确认分组的机制,可以提供可靠的服务,但是在下列一些复杂情况下,对于两端的连接是否正常,还是难以即时检测出的:
1) 如果两端的系统其中之一出现死机,另外一个是无法直接得知TCP连接的失效;
2) 如果在多级网络连接的情况下,中介网络线路的物理断开(如网线被切断、交换机断电等),TCP不会马上报告连接失效,而是做多次连接尝试;
以上两种情况下,TCP Socket的调用都会被正确返回而不报错误,与实际情况是不相符的。这样的时间可能持续几个小时甚至更长。
为了及时了解到通讯时TCP连接的有效性,一个常用的办法便是发送心跳消息。约定通讯的双方,如果在约定时间内没有消息通讯,发送一条心跳信息给对方。同时对信息的接收进行检测,如果超过约定时间没有接收到对方的任何消息,超时错误计数加1,同时设定一个超时计数的上限,如果错误计数未达到上限前收到对方的消息,超时错误计数清零;超过计数的上限,便告通讯失败,上报错误。
以上的方式自然希望能够灵活的设置到通讯模块中去,成为底层通讯控制的一部分。
2.3 异步、同步通讯方式
通讯模块只是采集系统的一部分,系统不可能一直在等待通讯的接收和发送中。所以,异步非阻塞的通讯方式也是显而易见的需求。
接收消息毫无疑问需要提供异步的方式,即通讯模块需要在接收消息时,通过某种方式给系统提供异步处理。
发送消息上,希望模块可以给使用者提供两种选择,一种是同步调用的方式,即需要完成发送消息才返回,类似于Win32的API函数::SendMessage();另外一种异步方式,调用者无须等待消息发送出去,就可以返回,类似于Win32的API函数::PostMessage()。
2.4 其它需求
以下是一些其它的功能需求:
1) 接收到多行消息时的正确处理;
2) 因为Buffer的限制,一条正常的消息可能分两次才接收完整的正确处理;
3) 对于接收到超长消息的忽略并报错处理;
4) 同步和异步方式发送消息可能在多线程下操作,需要考虑到发送操作的同步锁定问题,避免多条消息的混淆;
5) 错误处理:通讯的模块应该能够及时的报告各种错误。
6) 为了提高复用,希望设计的类TCPClient能够同时被服务器和客户端采用。
3 模块设计和实现
模块的框架设计参考下图:
图 3‑1 模块框架设计图
模块的使用者通过调用初始化函数Init异步启动消息读取和发送两个线程。通过Vi路径调用的方式运行起接收和发送线程函数DoRecvThread.vi和DoSendThread.vi,实现异步启动线程,参考下图:
图 3‑2 异步启动读取发送线程
类模块通过自定义事件给外部调用者发送多个事件的通知。下图展示的是该类的使用程序进行自定义事件注册并定义相应事件响应结构的一部分截图。
图 3‑3 使用TCPClient类
在DoRecvThread.vi的循环中,每次将TCP Read.vi的超时设置为检测心跳的间隔(CheckHBInt)。收到消息则将上一次接收内容切分后的剩余部分内容补充在前端,按回车换行分段,把分段的内容以自定义事件的方式通知外部的事件注册者。一旦接收超时,根据心跳设置对CheckHB计数并报RecvTimeout错误。通讯中出现Tcp错误,报错误事件并退出循环,另外Vi退出前再报一个退出事件。
DoSendThread.vi和DoRecvThread.vi类似,循环主体是等待发送消息队列(Dequeue Element.vi),其超时设置为心跳发送间隔(SendHBInt)。外部的异步调用发送消息SendMsgInQueue.vi就是使用Enqueue Element.vi来实现的。如果发送队列超时,则发出SendHB事件通知。在DoSendThread.vi和外部调用的SendMsgNow.vi中都通过调用同一个私有的内部函数SendMsg.vi发送消息,由于该函数是不可重入的,无须使用其它方式(如Semaphore共享锁)就可以实现多线程下的同步控制。
图 3‑4消息发送同步控制
3.2 类实现
最终整个通讯模块的实现类TCPClient.lvclass中所有的文件可以参考图 3‑5。
图3‑5 TCPClient.lvclass类的所有文件
下面对3.1设计中未介绍的其它函数做一些说明。
下面两图中是关于心跳设置的vi函数。
图 3‑6 心跳设置的私有Static控制变量
图 3‑7 心跳设置的控制函数(Get、Set方法)
这里采用Global变量来实现的心跳是否发送、检测以及发送和检测间隔这样的static控制变量(当然也可以用shift register 变量或Share Variable来实现)。将这些全局变量设置成私有的,再提供一些公有接口函数。由于操作这些变量都是通过公有控制接口函数来进行的,而这些函数不是可重入的函数。因此,可以很好的实现多线程下的同步,避免Global变量在多线程下读取不同步引起的诸多问题。
图 3‑8 析构函数和构造函数Exit.vi,Init.vi
其中Exit.vi和Init.vi相当于类的析构函数和构造函数,由于LabVIEW8.20面向对象的局限性,未能提供析构函数和构造函数,只能人为的约定调用这两个函数实现构造和析构。
4 结论
LabVIEW语言是一个简单易用、高度灵活的强大开发工具。伴随最新的LabVIEW8.20中带来的面向对象新元素,给我们全新的编程理念。LabVIEW8.20中类和对象(lvclass)的运用可以实现功能模块的良好封装。正是该技术的运用,使得设计实现的带心跳检测通用TCP/IP通讯类TCPClient.lvclass不仅满足了为大型深水拖曳水池试验系统的功能需求,还为该功能模块的复用和今后的扩展带来了极大的便利。
参考文献
[1] 网络设计师教程,信息产业部计算机软件专业技术资格和水平考试办公室组编,清华大学出版社。
[2] 《LabVIEW高级程序设计》,杨乐平等编著,清华大学出版社。
[3] 《重构——改善既有代码的设计》,Martin Fowler著,侯捷、熊节译,中国电力出版社。
[4] 《代码大全(第二版)》,McConnell著,金戈等译,电子工业出版社。
[5] “TCP Read.vi”, “TCP Write.vi” Topics in “LabVIEW Help”. LabVIEW8.20.
[6] LabVIEW二十周年技术秘籍,LabVIEW8.20中文文档光盘。