APP下载

基于C/S架构的数据压缩系统设计与实现

2019-11-05

自动化与仪表 2019年10期
关键词:服务端命名客户端

钱 晨

(光大环保技术研究院(南京)有限公司,南京210003)

随着环保业务的飞速发展,大量的现场运行数据需要被收集并存储。目前普遍采用扩充服务器的物理性能和提高其处理速度等方式,然而该方式存在很大的局限性,无法从根本上解决数据存储的问题,还极大地提高了硬件成本。因此,在有限的磁盘空间容纳更多的数据,成为亟需解决的问题。故在此背景下提出对历史数据进行压缩存储的方法和系统。该数据压缩系统采用C/S 模式,通过命名管道的通讯方式,实现客户端与服务端进程间通讯,将客户端获取的数据在服务端以插入的方式进行分类保存,充分地利用了磁盘空间,实现数据存储。

1 技术概述

在数据压缩系统的设计过程中,主要涉及以下关键技术:

1)管道 是Windows 系统进行进程间通讯的一种工具,可以分为匿名管道(Anonymous Pipes)和命名管道(Named Pipes)2 种类型[1]。同时,它也是一种具有两端点的通信通道,在每个端点上均可进行数据收发操作,如图1所示。虽然这2 种管道的功能基本一致,但匿名管道提供的服务有限,有着一定的局限性[2]。

图1 管道通信模型Fig.1 Pipe communication model

2)服务控制管理器 Windows 后台运行的任务统称为服务(Service),它们为用户提供可以在本地或远程计算机上的启动、停止、恢复应用程序等功能。实际上这些功能的实现均基于服务控制管理器SCM(service control manager)进行管理控制[3]。SCM拥有一个基于注册表的服务数据库,包含所有已安装服务程序的相关信息,通过可视化界面控制和管理服务的启动、停止和恢复等[4]。

2 系统总体设计

该系统以Window 7 32 位操作系统为平台,应用VC++ 6.0 基于控制台的Win 32 Console Application 开发。其中,C/S 通信模块采用Windows 命名管道技术,后台存储服务模块通过调用SCM 编程接口,实现开机自启动功能。

该系统的整体结构框架如图2所示。系统基于C/S 框架模型,客户端通过以太网通讯与OPC(OLE for processing control)服务器相连采集实时数据。服务端读取标签文件(Tag.cnt)和配置文件,完成初始化后创建命名管道并完成与客户端的连接,随后经过命名管道读取数据,并启动数据保存线程,将数据以插入的方式进行分类保存。

图2 系统整体结构框架Fig.2 Framework of system overall structure

3 系统客户端功能设计

在编写系统客户端(EBHKcore)程序之前,先进行数据库配置:

①点击OPC 表,设置OPC 服务器的名称、用户名、登录密码及OPC 域名等。如果要连接远程OPC服务器,填写对方的IP 地址即可。

②点击Tag 表,配置OPC 标签点。项名由OPC服务器中的通道号+设备号+点名组成,必须与OPC服务器中的信息一一对应,否则在读取该点数据时会发生错误;记录方式(保存方式)分为按时间间隔保存和按误差超限保存;记录间隔用于表示多长时间保存一次;误差用于表示超过误差阈值通过管道发送数据。

数据库配置完成后,启动EBHKcore,读取数据库Tag 表中的信息(如图3所示),按TagID 排序生成Tag.cnt 文件,该文件主要用于后台历史数据压缩存储服务。紧接着,按照Tag 表中的记录方式、记录间隔及误差,指导命名管道向后台存储服务发送满足条件的数据。

对于Structs.h 头文件中声明的3 个重要的结构体,其含义以及声明代码如下:

1)PIPEHEADER(管道头结构体) 顾名思义就是放置在发送管道的首端,用于记录发送缓冲区中数据结构的个数、数据结构的长度,以及管道发送字节的总长度。

2)LOGRECORD (数据结构) 用于存放管道中即将发送变量的各种重要信息,如TagID、质量码、数值,以及数值刷新时间等。

3)PIPERET (管道返回结构体) 结构与管道头类似,顾名思义是从服务端返回的校验结构体,用于判断客户端的数据发送是否成功,校验客户端发送数据的可靠性和完整性,确保服务端能够顺利地进行数据存储。

将这些结构体变量作为参数,放入客户端部分的核心函数CallNamedPipe()中。该函数用于客户端进程请求服务端连接,具体的参数含义以及调用代码如下:

4 系统服务端功能设计

该数据压缩系统服务端的服务控制管理模块主要包含3 个函数:服务程序主函数(main),服务入口函数(service_main),控制服务处理函数(Service Control handle)。服务端设计流程如图4所示。

图4 服务端设计流程Fig.4 Server design flow chart

①服务程序主函数(main) 初始化一个服务表入口(SERVICE_TABLE_ENTRY)结构体数组(提供一个service_main 函数),然后调用服务控制分派(StartServiceCtrlDispatcher)函数连接程序主线程到服务控制管理程序;②服务入口函数(service_main) 执行服务初始化任务,由于此处只需要一个服务进程完成历史数据压缩存储,所以对应一个服务入口;③控制服务处理函数(Service Control handle) 调用注册服务控制处理(RegisterServiceCtrlHandler)函数来注册它的控制处理器(Control Handler)函数。

服务启动(ServiceStart)成功后,进行保存前的初始化工作,读取配置文件和标签文件(Tag.cnt),如图4所示。具体地读ini 配置文件获取his 文件的保存路径,读Tag.cnt 文件初始化m_mapIDToType 映射(键:ID 值:Type)。随后便可由服务端循环创建非阻塞模式命名管道不停地接收客户端的数据。

命名管道服务端主要包含2 个重要函数,分别为创建命名管道(CreateNamedPipe)函数和连接命名管道(ConnectNamedPipe)函数。在创建命名管道前,先定义并初始化SECURITY_ATTRIBUTES 结构体,作为CreateNamedPipe()函数的实参,具体的初始化代码如下:

然后,在服务端创建一个命名管道,具体参数含义及代码如下:

命名管道创建并初始化后,进行管道连接操作,由于创建管道时的打开模式为FILE_FLAG_OVERLAPPED,所以必须初始化OVERLAPPE 结构体,其关键语句如下:

OVERLAPPE 结构体初始化后,服务端连接管道,同时等待客户端连接请求的到来。该过程的具体实现代码如下:

如果命名管道连接成功,创建并进入实例线程InstanceThread,如图4所示,读取管道中客户端发送的数据流,并把数据放入缓冲数组byBuf 中。如果实际读取得字节长度≥管道头中记录的发送字节总长度,把管道校验结构体pr 的wCode 成员变量置位并发送给客户端释放连接。同时,服务端可以进行保存,并启动保存线程SaveThread,按管道头中的数据类型进行分类保存,保存过程中先创建、初始化历史文件(his),再通过缓存记录位号数据、位号刷新时刻及其在文件中的存储位置,最后根据记录的信息在文件中对应的位置处写入位号数据和刷新时刻。如此反复,将管道中发送过来的每一个位号的信息都写入his 文件中按顺序存储。

要实现服务端程序EBHKHIS.exe 开机自启动,无需人为的手动干预,就要以管理员身份打开命令提示符窗口,输入以下脚本:

服务添加后,退出cmd,打开控制面板→管理工具→服务,就会发现EBHKHIS Service 已经在后台启动。

5 数据存储设计

历史文件(his)按照日期每天零点进行创建,位号数据依据数值刷新时刻以插入的形式存储在his中。his 文件的存储结构如图5所示,其包括文件头、数据块信息以及具体位号数据。

由图可见,文件头由多个变量组成,占1024 B;数据块信息包含多个标签(Tag),每个Tag 由多个字段组成,占594 B。当需要存储下一个Tag 时,文件指针实现自增长,将其存储在已有Tag 之后。图5还展示了Tag1 和Tag2 的存储结构,即由数值刷新时刻(经过编码)和对应的数值组成。其中,数值刷新时刻是将TagID、 质量码叠加到数值刷新时刻再进行编码后形成的,所以说历史文件中存储的是经过处理的数据,而非文本形式的原始数据。这样,能够防止历史文件中的数据被泄露,保证其安全性。

举例说明,his 文件存储结构如图5所示,Tag数据块信息中Tag1 的dwLastTagPos 为0,即在历史文件的位置0 处存储,如图5下方所示的Tag1;Tag数据块信息中Tag2 的dwLastTagPos 为12,即在历史文件的位置12 处存储,如图5下方所示的Tag2。依此类推,采用这样紧凑的方式存放数据,能够充分地利用存储空间,避免存储空间的浪费。

图5 his 文件存储结构Fig.5 His file storage structure

针对某特定的TagID,可以经过命名管道获取多个数据,多个数据可以是一次接收到的或者也可以是多次接收到的,不同的数据可以具有相同或不同的数值刷新时刻和数值(value)。举例说明,假设Tag1 的TagID=ID1。若接收到一个新的数据,其TagID 也为ID1,则认为它们同属于Tag1,可以将其按照数值刷新时刻进行插入。

若新收到的数据的数值刷新时刻(dwNewSec)>已存储的数值刷新时刻(dwLastSec),则将Tag 数据块信息部分Tag1 已存储的数值刷新时刻(dwLast-Sec)更新为新收到的数据的数值刷新时刻,将Tag数据块信息部分Tag1 已存储的数值存储位置(dwLastTagPos)更新为新收到的数据的数值存储位置。也就是说,Tag 数据块信息部分各个Tag 的dwLastTagPos 和dwLastSec,是根据最后接收到的数据而不断更新的,并且根据这些更新的信息指导接下来的数据存储。

举例说明,如果又收到一个新的Tag1 数据,可以按顺序依次存储编码后的数值刷新时刻t12 以及对应的Tag 数值V2。由图5可见,将Tag 数据块信息中Tag1 的dwLastTagPos 更新为24,dwLastSec 更新为T12,并在历史文件的位置24 处(即图5下方所示的Tag2 之后) 存储与图5下方所示的Tag1 相类似的编码后的T12 和V2。这样就可以将Tag1 的所有数据进行存储,并且以这种插入的存储方式进行存储能够节省存储空间,使得存储空间的利用率更高。若dwNewSec=dwLastSec,则将对应位置处的数值进行更新即可。

另外,可以为历史文件设定预留空间,假设为1024 B。可理解,如果dwLastPos+预留空间1024 B>文件长度(B),说明预留空间不足,此时需要额外增加空间,一般是512 kB 的整数倍。再者,还可以根据需求设定时间跨度,定时删除部分历史文件,减少磁盘的占用空间。

6 结语

通过VC++编程的具体实例,介绍了SCM 接口技术、命名管道在进程间通信的应用,以及后台数据存储的方法。该方法实现了从客户端获取实时数据,把满足条件的数据通过管道通信的方式发送至后台存储服务,最后以插入的方式进行分类保存的功能。该系统经过在线测试和现场试运行阶段后,已正式投入使用,并在各个项目公司取得良好的效果。

猜你喜欢

服务端命名客户端
你的手机安装了多少个客户端
你的手机安装了多少个客户端
命名——助力有机化学的学习
如何看待传统媒体新闻客户端的“断舍离”?
新时期《移动Web服务端开发》课程教学改革的研究
有一种男人以“暖”命名
为一条河命名——在白河源
新华社推出新版客户端 打造移动互联新闻旗舰
河鲀命名小考
摸清黑客套路防范木马侵入