文章目录海萤物联网教程物联网RPC框架Go DCOM简介开源安装基础概念资源ID协议号管道通信模型APILoad模块载入Receive 接收数据Register服务注册Call同步调用请求和应答数据格式海萤物联网教程物联网RPC框架Go DCOM本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明.在线文档地址简介RPCRemote Procedure Call远程过程调用。使用RPC可以让一台计算机的程序程调用另一台计算机的上的程序。RPC通过把网络通讯抽象为远程的过程调用调用远程的过程就像调用本地的子程序一样方便从而屏蔽了通讯复杂性使开发人员可以无需关注网络编程的细节将更多的时间和精力放在业务逻辑本身的实现上提高工作效率。DCOMDevice Communication Protocol(DCOM)设备间通信协议。DCOM是针对物联网使用场景开发的RPC框架主要有如下特点协议开销极小仅4字节。物联网很多场景都是几十字节的短帧RPC协议本身的开销如果过大会导致在这些场景无法应用可以跨语言通信。DCOM协议在设计上与语言无关无论CGolangPython等都可以使用DCOM可以跨通信介质通信。DCOM协议可以工作在以太网串口wifi小无线等一切通信介质之上在海萤物联网中节点间使用DCOM来通信。本文介绍Go语言开发的DCOM包的使用方法。开源github上的项目地址gitee上的项目地址安装推荐使用go modgithub.com/jdhxyy/dcom安装好后在项目中即可导入使用importhttps://github.com/jdhxyy/dcom基础概念资源ID资源ID又称服务号Resource ID简称RID。节点使用服务号开放自己的能力或者资源。服务号取值1-1023共1023个数。比如某个节点是智能插座开放两个能力插座开关插座当前状态则可对外提供两个服务服务号说明1插座开关2插座当前状态协议号协议号protocol每个协议可以绑定一组资源ID不同协议下的资源ID可以重复。管道管道pipe。DCOM通信时数据流可以绑定到某个管道上DCOM支持多个管道同时通信。管道可以是UDP端口、TCP端口、串口、4G、无线等每个管道都有独立的发送和接收回调。管道号不能为0是64位数。通信模型DCOM主要有两种通信模型有应答通信无应答通信。常用的是有应答的通信如果没有收到应答DCOM在通信时会尝试重传。每次使用Call函数与目的节点通信都是一个新的会话每个会话都有唯一的token值来保证数据不会串扰。API// Load 模块载入funcLoad(param*LoadParam)// Receive 接收数据// 应用模块接收到数据后需调用本函数// 本函数接收帧的格式为DCOM协议数据funcReceive(protocolint,pipeuint64,srcIAuint64,bytes[]uint8)// Register 注册服务回调函数funcRegister(protocolint,ridint,callback CallbackFunc)// Call RPC同步调用// timeout是超时时间,单位:ms.为0表示不需要应答// 返回值是应答字节流和错误码.错误码非SystemOK表示调用失败funcCall(protocolint,pipeuint64,dstIAuint64,ridint,timeoutint,req[]uint8)([]uint8,int)辅助函数// StructToBytes 结构体转字节流funcStructToBytes(sinterface{})([]uint8,error)// BytesToStruct 字节流转结构体 结构体中的元素首字母要求大写// s是结构体指针,保存转换后的结构体funcBytesToStruct(data[]uint8,sinterface{})error数据结构// LoadParam 载入参数typeLoadParamstruct{// 块传输帧重试间隔.单位:msBlockRetryIntervalint// 块传输帧重试最大次数BlockRetryMaxNumint// API接口// 是否允许发送IsAllowSend IsAllowSendFuncByPipeFunc// 发送的是DCOM协议数据Send SendByPipeFunc}// IsAllowSendFuncByPipeFunc 某管道是否允许发送函数类型typeIsAllowSendFuncByPipeFuncfunc(pipeuint64)bool// SendByPipeFunc 向指定端口发送函数类型typeSendByPipeFuncfunc(protocolint,pipeuint64,dstIAuint64,bytes[]uint8)// CallbackFunc 注册DCOM服务回调函数// 返回值是应答和错误码.错误码为0表示回调成功,否则是错误码typeCallbackFuncfunc(pipeuint64,srcIAuint64,req[]uint8)([]uint8,int)系统错误码const(// 正确值SystemOK0// 接收超时SystemErrorRxTimeout0x10// 发送超时SystemErrorTxTimeout0x11// 内存不足SystemErrorNotEnoughMemory0x12// 没有对应的资源IDSystemErrorInvalidRid0x13// 块传输校验错误SystemErrorWrongBlockCheck0x14// 块传输偏移地址错误SystemErrorWrongBlockOffset0x15// 参数错误SystemErrorParamInvalid0x16)Load模块载入在使用DCOM前必须要初始化。DCOM支持重传所以在初始化时需输入重传间隔以及重传最大次数。DCOM与通信介质无关不同介质可定义不同的管道号。应用程序需要在是否允许发送函数IsAllowSend 以及发送函数Send中编写不同管道的操作。示例某节点有两个管道funcmain(){varparam dcom.LoadParam param.BlockRetryMaxNum5param.BlockRetryInterval1000param.IsAllowSendisAllowSend param.Sendsend dcom.Load(param)}funcisAllowSend(pipe uin64)bool{ifpipe1{returnisPipe1AllowSend()}else{returnisPipe2AllowSend()}}funcsend(protocolint,pipeuint64,dstIAuint64,data[]uint8){ifpipe1{pipe1Send(data)}else{pipe2Send(data)}}protocoldstIA等字段根据需求处理。Receive 接收数据应用程序接收到数据需要调用Receive函数将数据发送给DCOM。示例某节点有两个管道都可接收funcpipe1Receive(data[]uint8){dcom.Receive(0,1,0x2140000000000101,data)}funcpipe2Receive(data[]uint8){dcom.Receive(0,2,0x2140000000000101,data)}协议号protocol示例中填写的是0应用时根据实际场景填写。Register服务注册节点可以通过注册服务开放自身的能力。示例假设节点2140::101是智能插座提供控制和读取开关状态两个服务dcom.Register(0,1,controlService)dcom.Register(0,2,getStateService)// controlService 控制开关服务// 返回值是应答和错误码.错误码为0表示回调成功,否则是错误码funccontrolService(pipeuint64,srcIAuint64,req[]uint8)([]uint8,int){ifreq[0]0{off()}else{on()}returnnil,dcom.SystemOK}// getStateService 读取开关状态服务// 返回值是应答和错误码.错误码为0表示回调成功,否则是错误码funcgetStateService(pipeuint64,srcIAuint64,req[]uint8)([]uint8,int){return[]uint8{state()},dcom.SystemOK}Call同步调用// Call RPC同步调用// timeout是超时时间,单位:ms.为0表示不需要应答// 返回值是应答字节流和错误码.错误码非SystemOK表示调用失败funcCall(protocolint,pipeuint64,dstIAuint64,ridint,timeoutint,req[]uint8)([]uint8,int)同步调用会在获取到结果之前阻塞。节点可以通过同步调用调用目标节点的函数或者服务。timeout字段是超时时间单位是毫秒。如果目标节点超时未回复则会调用失败。如果超时时间填0则表示不需要目标节点回复。示例2141::102节点控制智能插座2141::101开关状态为开resp,errCode:dcom.Call(0,1,0x2140000000000101,3000,[]uint8{1})示例2141::102节点读取智能插座2141::101开关状态resp,errCode:dcom.Call(0,2,0x2140000000000101,3000,nil)iferrCodedcom.SystemOK{fmt.println(开关状态:,resp[0])}请求和应答数据格式DCOM通信双方发送的数据流都是二进制请求req和应答resp的数据类型都是[]uint8。二进制不利于应用处理所以会将二进制转换为其他数据类型来处理。常用的有以下三种结构体json字符串在物联网中硬件节点的资源有限且大部分都是使用C语言编写代码。所以建议使用C语言结构体来通信。结构体约定使用1字节对齐小端模式。比如海萤物联网的ntp服务提供的数据结构struct{// 时区uint8 TimeZone uint16 Year uint8 Month uint8 Day uint8 Hour uint8 Minute uint8 Second// 星期uint8 Weekday}DCOM中提供结构体和二进制转换的函数// StructToBytes 结构体转字节流funcStructToBytes(sinterface{})([]uint8,error)// BytesToStruct 字节流转结构体 结构体中的元素首字母要求大写// s是结构体指针,保存转换后的结构体funcBytesToStruct(data[]uint8,sinterface{})error注意go中定义这些结构体要求属性必须要大写否则转换成二进制会失败。示例将时间结构体转换为二进制// ACK格式typeTimestruct{// 时区TimeZoneuint8Yearuint16Monthuint8Dayuint8Houruint8Minuteuint8Seconduint8// 星期Weekdayuint8}t:Time{8,2021,4,1,7,32,1,4}data:StructToBytes(t)示例将二进制转换为结构体vart Time err:BytesToStruct(data,t)约定DCOM通信中如果直接使用二进制通信建议使用大端模式。如果使用结构体通信结构体编码是小端模式。如果混合传输既有二进制也有结构体二进制部分使用大端结构体部分使用小端。