Modbus-RTU/TCP规约 | 报文解析 | 组织报文与解析报文(C++)

文章目录

  • 一、MODBUS规约
    • 1.MODBUS-RTU规约
    • 2.MODBUS-TCP规约
  • 二、报文解析
    • 1.MODBUS-RTU报文帧解析
    • 2.MODBUS-TCP报文帧解析
  • 三、C++代码实现组织报文与解析报文

一、MODBUS规约

  Modbus规约是一种广泛使用的串行通信协议(应用层报文传输协议),用于工业环境中控制器和电子设备之间的数据交换。

Modbus协议的特点

  • Modbus是一种应用广泛的问答式通信规约

    信息交换是以主站采取主动实现的,即由主站启动交换。所有的一个完整交换由下行和上行两个报文组成:

    • 下行报文:主站发出的一个请求到从站
    • 上行报文:从站发回的一个回答到主站
  • MODBUS 是一种主/从架构

    Modbus协议中,主设备发起通信请求,而从设备响应这些请求。

  • Modbus设备中的数据存储在寄存器中,包括位寄存器和16位寄存器,每个寄存器都有一个唯一的地址

  • Modbus支持多种通信方式,包括Modbus RTUModbus TCPModbus RTU主要用于串口通信,而Modbus TCP适用于以太网通信

  • Modbus协议的帧格式简单紧凑,易于理解和实现。Modbus RTU帧以CRC校验(错误检测机制)结束,确保数据的完整性。而Modbus TCP帧则不包含CRC校验,因为TCP/IP协议本身提供了错误检测和重传机制。

  • 设备地址唯一性:在Modbus网络中,每个从设备都有一个唯一的地址,确保了通信的准确性

  • 支持多种电气接口:Modbus可以支持多种电气接口,如RS-232、RS-485等,并且可以在多种介质上传输,包括双绞线、光纤、无线等

1.MODBUS-RTU规约

报文格式

  所有交换的 RTU 类型报文(帧),无论上/下行,具有相同的结构:

从站号功能码数据区CRC16校验和
1字节1字节n字节2字节

每帧报文包含 4 种类型的信息:

  • 从站号

    从站号为 1 字节,取值范围为 0~FFH.例外的,如果此值为 0,则作为主站的广播信文标识.因此,物理上使用的从站号只能在 01H~FFH 之间(即1~255 之间)

  • 功能码

    功能码为 1 字节,它被用来选择一个命令(读、写或回答校验是否正确等),有效功能码范围为 1~255 之间。Modbus标准协议中可使用如下功能码:

    功能码(十进制)含义
    01读线圈状态
    02读离散输入量寄存器
    03读多个保持寄存器
    04读输入型寄存器
    05强制单个线圈
    06写单个寄存器
    15强制多个线圈
    16写多个保持寄存器
    20读变量
    21写变量

    上述是Modbus协议支持的所有功能码(标准),并不代表所有支持Modbus-RTU通信协议的设备支持所有功能码。比如:

    • 安科瑞ADL400导轨式多功能电能表的RS485 通信接口支持 MODBUS-RTU 通信协议,但只支持MODBUS-RTU 协议中的 03H 命令与 10H 命令,03H 为读多个寄存器,10H为写多个寄存器
    • 高特ESBCM的物理接口同时支持485接口与TCP接口,支持的功能码有02、03、04、06、41
    • 汇川PCS支持采用 MODBUS RTUMODBUS TCP/IP 通讯规约实现PCS与EMS之间的通信。支持的功能码有03、06
    • TX-CSL60-230除湿机具有RS-485通信口,支持功能码03、06

    功能码解读

    读线圈状态–功能码01

    • 线圈通常指的是设备的输出线圈,如继电器或接触器的线圈
    • 这个功能用于读取这些输出线圈的状态,即它们是被激活(闭合)还是未激活(断开)

    读离散输入量寄存器–功能码02

    • 这些寄存器一般是只读位寻址寄存器,一般可用于存储设备的告警信息,如:高特ESBCM

      在这里插入图片描述

    读多个保持寄存器–功能码03

    • 这些寄存器是读写寻址寄存器,可接受多种数据类型,只要每一帧报文要采集的点位对应的数据类型相同即可,如安科瑞ADL400电能表

      在这里插入图片描述

    读输入型寄存器–功能码04

    • 这些寄存器为只读字寻址寄存器,输入型寄存器通常包含模拟输入信号的数字化值,如从传感器或变位器获取的信息,如高特ESBCM

      在这里插入图片描述

    强制单个线圈–功能码05

      字面意思感觉是遥控的功能码,不确定

    Modbus的数据传输被定义为对以下4个存储块的读写:(方便理解功能码的具体含义)

    • 线圈–操作单位为1位字的开关量,PLC的输出位,在Modbus中可读可写

      一般指的是触点的“开”与“关”的状态

    • 离散量–操作单位为1位字的开关量,PLC的输入位,在Modbus中只读

    • 输入寄存器-- 操作单位为16位字(两个字节)数据,PLC中只能从模拟量输入端改变的寄存器,在Modbus中只读

    • 保持寄存器-- 操作单位为16位字(两个字节)数据,PLC中用于输出模拟量信号的寄存器,在Modbus中可读可写

  • 数据区

    数据区为 n 字节,它包含与功能码相关的一串十六进制数据,

    如果是读,则包含寄存器起始地址与寄存器数量,数据格式基本一致。

    • 寄存器起始地址指的是要通讯设备的寄存器地址,查设备的使用说明书就可以得到,如:

    在这里插入图片描述

    • 寄存器数指的是一帧报文连续读多少个寄存器
      在这里插入图片描述

    如果是写,则需要分写单个与写多个

    • 示例
      在这里插入图片描述
  • CRC16校验码和

    CRC16校验码通常被添加到数据帧的末尾,用于接收端校验数据的完整性。

    CRC在线计算网址为http://www.ip33.com/crc.html,该报文帧为01 03 00 01 00 01 D5 CA
    在这里插入图片描述

2.MODBUS-TCP规约

  ModbusMODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IPModbus协议:Modbus-TCP。与传统的串口方式,MODBUS TCP插入一个标准的MODBUS报文到TCP报文中,不再带有数据校验和地址

  Modbus-TCP结合了Modbus应用协议和TCP/IP网络协议,主要用于工业自动化领域,允许设备通过以太网进行数据交换。

以下是Modbus-TCP通信规约的关键点:

  • 基于TCP/IPModbus-TCP运行在TCP/IP网络上,使用TCP作为传输层协议,确保数据的可靠传输

  • MBAP报文头Modbus-TCP的数据帧包含一个Modbus应用协议(MBAP)报文头,长度为7字节,包含了事务处理标识、协议标识、长度和单元标识符等信息

  • 端口号:Modbus-TCP通信通常使用TCP端口号502,这是由互联网编号分配管理机构(IANA)为Modbus协议指定的端口号

  • 主从架构:Modbus-TCP遵循主从架构,有一个主站(客户端)和多个从站(服务器)。主站发起请求,从站响应请求

  • 数据表示:Modbus-TCP在以太网TCP/IP数据包中传输Modbus RTUASCII格式的数据帧,但不包含数据校验和地址

  • 功能码:Modbus-TCP保留了Modbus RTUASCII的功能码,用于执行不同的操作,如读取和写入寄存器

  • 异常响应:当从站设备无法正确处理主站的请求时,会返回异常功能码和错误代码,以指示错误类型

  • 通信过程:Modbus-TCP的通信过程包括建立TCP连接、发送Modbus报文、接收响应报文和关闭TCP连接

  • 数据帧格式Modbus-TCP的数据帧格式包括MBAP报文头和PDU(协议数据单元),其中PDU包含Modbus功能码和数据

Modbus-TCP报文帧格式

  Modbus-TCP的数据帧可分为两部分:MBAP+PDU

在这里插入图片描述

  • MBAP报文头,长度为7个字节,组成如下:

    序号名称字节长度说明
    1事务处理标识2可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文
    2协议标识2协议标识符,00 00表示ModbusTCP协议
    3长度2表示后续字节数,单位为字节
    4单元标识符1设备标识符,即从站地址
  • 帧结构PDU,由功能码与数据区组成,

    • 功能码为1个字节,同上

    • 数据区长度不定,同上

二、报文解析

1.MODBUS-RTU报文帧解析

  以ADL400导轨式多功能电能表为例,解析报文的请求(查询报文帧)与响应(返回数据帧)。

读A相电流数据

请求/返回报文数据
RTU报文_请求01 03 00 64 00 01 C5 D5
  • 01:从机地址
  • 03:读功能码
  • 00 64:寄存器起始地址
  • 00 01:读取寄存器个数
  • C5 D5:CRC16校验码和
请求/返回报文数据
RTU报文_回复01 03 02 03 B2 38 C1
  • 01:从机地址

  • 03:读功能码

  • 02:表示后面有两个字节长度的数据,如果一个寄存器占两个字节,则数据长度等于寄存器量*2

  • 03 B2:对应点位的数据,以十六进制表示,这里是A相电流数据

    处理如下:03 B2(十六进制) = 946(十进制)

    计算:946 * 0.01 = 9.46,单位:A

  • 38 C1:循环冗余校验码

读总有功电能数据

请求/返回报文数据
RTU报文_请求01 03 00 00 00 02 C4 0B
  • 01:从机地址
  • 03:读功能码
  • 00 00:寄存器起始地址
  • 00 02:读取寄存器个数
  • C4 0B:CRC16校验码和
请求/返回报文数据
RTU报文_返回01 03 04 00 00 30 26 6F 9E
  • 01:从机地址

  • 03:读功能码

  • 04:表示后面有4个字节长度的数据,如果一个寄存器占两个字节,则数据长度等于寄存器量*2

  • 00 00 30 26 :对应点位的数据,需要用4个字节,以十六进制表示

    高位:00 00(16 进制) = 0 (10 进制)

    低位:30 26(16 进制) = 12326 (10 进制)

    因此该仪表二次测有功电能为:(0×65536 + 12326)*0.01 = 123.26;单位:kWh

2.MODBUS-TCP报文帧解析

  以汇川IES1000-M-04-100 系列储能变流器PCS为例,解析报文的请求(查询报文帧)与响应(返回数据帧)。

读取电网相电压与输出相电压

在这里插入图片描述

请求/返回报文数据
TCP报文_请求00 00 00 00 00 06 01 03 ED 64 00 06
  • 00 00:事务处理标识
  • 00 00:表示ModbusTCP协议
  • 00 06:表示后续字节数为6个字节
  • 01:从机地址
  • 03:读功能码
  • ED 64:寄存器起始地址
  • 00 06:寄存器的数量为6个
请求/返回报文数据
TCP报文_返回00 00 00 00 00 0F 01 03 0C 09 7C 09 7D 09 8A 00 06 00 0A 00 0C
  • 00 00:事务处理标识

  • 00 00:表示ModbusTCP协议

  • 00 0F:表示后续字节数为15个

  • 01:从机地址

  • 03:读功能码

  • 0C:表示后面有12个字节长度的数据,如果一个寄存器占两个字节,则数据长度等于寄存器量*2 (6*2=12=0CH

  • 09 7C 09 7D 09 8A 00 06 00 0A 00 0C

    • A相电网相电压

      处理如下:09 7c(十六进制) = 2428(十进制)

      计算:2428 * 0.1 = 242.8,单位:V

    • B相电网相电压

      处理如下:09 7D(十六进制) = 2429(十进制)

      计算:2429 * 0.1 = 242.9,单位:V

    • C相电网相电压

      处理如下:09 8A(十六进制) = 2442(十进制)

      计算:2442 * 0.1 = 244.2,单位:V

    • A相输出相电压

      处理如下:00 06(十六进制) = 6(十进制)

      计算:6 * 0.1 = 0.6,单位:V

    • B相输出相电压

      处理如下:00 0A六进制) = 10(十进制)

      计算:10 * 0.1 = 1,单位:V

    • C相输出相电压

      处理如下:00 0C(十六进制) = 12(十进制)

      计算:12 * 0.1 = 1.2,单位:V

写寄存器报文帧解析

  • 写单寄存器

    在这里插入图片描述

  • 写多寄存器
    在这里插入图片描述

三、C++代码实现组织报文与解析报文

  执行逻辑

  • 首先,从数据库表中获取规约的编号,如:ptl_modbus_cj

  • 然后,加载规约动态库,得到该规约对应的组织数据与解析数据的函数

    QLibrary            m_LibPtl;  //加载规约的libchar chParseFuncName[SIZE_SN] = "parseData"; // 解析报文函数
    char chBuildFuncName[SIZE_SN] = "buildData"; //组织报文函数m_LibPtl.setFileName(m_qsPtlLibName);       //设置外部动态链接库文件名
    //调用QLibrary对象的load()方法来加载指定的库。如果库文件存在并且可以成功加载,此方法返回true;否则返回false。
    if(m_LibPtl.load())     //加载动态链接库
    {//resolve()方法被用来查找库中指定名称的函数,返回一个指向该函数的指针m_pFuncParseData = (PFunc_ParseData)(m_LibPtl.resolve(chParseFuncName));  //得到规约对应的解析数据与组织数据的函数m_pFuncbuildData = (PFunc_BuildData)(m_LibPtl.resolve(chBuildFuncName));}
    
  • 通道通信发送数据处理

    • 调用规约报文组织函数组织报文

      m_pFuncbuildData(m_SPTLRecvSenddata, dynamic_cast<CUnitBase*>(m_pCurrentUnit));  //调用规约报文组织函数组织报文
      
      PTL_API void buildData(S_RecvSendData &sRecvSendData, CUnitBase* pUnit)  //组织报文
      {S_PtlVariant sVariant;memset(&sVariant, 0, sizeof(S_PtlVariant));if(pUnit != NULL){sVariant.pUnit = pUnit;sVariant.pBySendBuffer = sRecvSendData.pSendBuf;sVariant.uwSendDataLen = sRecvSendData.uiSendLen;sVariant.bMainChannel = sRecvSendData.bMainChannel;sVariant.byChannelMode = sRecvSendData.byChannelMode;sVariant.byChannelIndex = sRecvSendData.byChannelIndex;sVariant.pPtlFlag = (S_PtlFlag*)(pUnit->getTUFlagBuffer());  // 获取规约标志位使用的缓冲区sendFrame(sVariant); //组织数据sRecvSendData.uiSendLen = sVariant.uwSendDataLen;}
      }
      

      根据组织发送什么类型的报文,调用不同的组织函数

      • 遥测查询帧
      • 遥信查询帧
      • 遥脉查询帧
      • 遥调帧
      • 遥控帧
      • 校时命令

      以组织发送遥测查询帧为例,

      void buildCmdQueryYC(S_PtlVariant &sVariant)
      {if(sVariant.uwSendDataLen + 14 > sVariant.uwMaxSendLen){return;}quint16 uwTUAddr = sVariant.pUnit->getTUAddress();if(uwTUAddr > 255){return;}quint8 byDataLen = 0;quint8* pBySendBuffer = sVariant.pBySendBuffer + sVariant.uwSendDataLen;//校验和开始地址quint8* pSumPos = pBySendBuffer;pBySendBuffer[0] = uwTUAddr; //从站地址//功能码if(sVariant.pPtlFlag->byQueryYCNum >= sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec.size()){sVariant.pPtlFlag->byQueryYCNum = 0;}const S_ModbusQuery* pModbusQuery = sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec[sVariant.pPtlFlag->byQueryYCNum];pBySendBuffer[1] = pModbusQuery->byFUNCCODE;//寄存器起始地址HLquint16 uwRegStartAddr = pModbusQuery->uwStartAddr;if(MODBUS_BYTEORDER_TYPE_H_L == sVariant.pPtlFlag->pPtlModbus->pModbus->byRegisterByteOrder){uwRegStartAddr =  qByteOrderConvert(uwRegStartAddr);}memcpy(pBySendBuffer + 2, &uwRegStartAddr, 2);//寄存器数量HLquint16 uwRegisterNum = pModbusQuery->byRegisterNum;uwRegisterNum = qByteOrderConvert(uwRegisterNum);memcpy(pBySendBuffer + 4, &uwRegisterNum, 2);byDataLen = 6;quint16 uwCheckSum = 0;if(buildCheckSum(sVariant, pSumPos, byDataLen, uwCheckSum)) //有校验和{memcpy(pBySendBuffer + byDataLen, &uwCheckSum, 2);byDataLen += 2;}buildTCPHead(sVariant, byDataLen);    //TCP头sVariant.uwSendDataLen += byDataLen;sVariant.pPtlFlag->tLastSendYCTime = qGetCurrentTime();sVariant.pPtlFlag->byQueryYCNum++ ;sVariant.pPtlFlag->byQueryType = QUERYDATA_TYPE_YCDATA;sVariant.pPtlFlag->sendCmdQueryYC = 0;sVariant.pPtlFlag->bWaitDataReturn = 1;sVariant.pPtlFlag->byResentCount = sVariant.uwSendDataLen;
      }
      

      其中,组织校验和与组织MBAP头的函数分别为

      void buildTCPHead(S_PtlVariant &sVariant, quint8& byDataLen)    //组织TCP头 调用该函数之前 其余报文已经组织完毕
      {if(MODBUS_TYPE_TCP == sVariant.pPtlFlag->pPtlModbus->pModbus->byType){memmove(sVariant.pBySendBuffer + 6, sVariant.pBySendBuffer, byDataLen);sVariant.pBySendBuffer[0] = sVariant.pPtlFlag->pPtlModbus->pModbus->uwTCPHeadHigh;sVariant.pBySendBuffer[1] = sVariant.pPtlFlag->pPtlModbus->pModbus->uwTCPHeadLow;sVariant.pBySendBuffer[2] = 0;sVariant.pBySendBuffer[3] = 0;quint16 uwDataLen = byDataLen;uwDataLen = qByteOrderConvert(uwDataLen);memcpy(sVariant.pBySendBuffer + 4, &uwDataLen, sizeof(quint16));byDataLen += 6;}
      }bool buildCheckSum(S_PtlVariant &sVariant, quint8 *pStartCheckPost, quint8 byDataLen, quint16 &uwCheckSum)    //组织校验和报文 返回字节转换后的校验后
      {//校验if(MODBUS_TYPE_RTU == sVariant.pPtlFlag->pPtlModbus->pModbus->byType){quint8 byCheckSumType=sVariant.pPtlFlag->pPtlModbus->pModbus->byCheckCodeMode;if(CHECK_CODE_CRC == byCheckSumType)    //CRC校验{uwCheckSum = getCheckCRC16SUM(pStartCheckPost, byDataLen);}else    //累加和校验{uwCheckSum = getCheckCumlationSum(pStartCheckPost, byDataLen);}if(MODBUS_BYTEORDER_TYPE_H_L == sVariant.pPtlFlag->pPtlModbus->pModbus->byCCBYTEORDER){uwCheckSum = qByteOrderConvert(uwCheckSum);}return true;}return false;
      }
      
    • 组织好报文后,基于通道通讯类型,分别调用串口或网络的发送数据接口

      switch(m_pChannel->byChannelCOMType)
      {case CHANNEL_TYPE_TCP_CLIENT:case CHANNEL_TYPE_TCP_SERVER:case CHANNEL_TYPE_UDP_CLIENT:case CHANNEL_TYPE_UDP_SERVER:m_SocketMutex.lock();m_SPTLRecvSenddata.iSendedLen = m_pNetComm->sendData(m_pSocketHandle, m_SPTLRecvSenddata.pSendBuf, m_SPTLRecvSenddata.uiSendLen);m_SocketMutex.unlock();if(m_SPTLRecvSenddata.iSendedLen > 0){if(m_qsPtlLibName.contains("_zf")){//ljx debugqMSleep(50);}else{//                qMSleep(30);}}break;case CHANNEL_TYPE_COM_485:case CHANNEL_TYPE_COM_232:{m_SPTLRecvSenddata.iSendedLen = m_pSerial->sendData(m_SPTLRecvSenddata.pSendBuf,static_cast<int>(m_SPTLRecvSenddata.uiSendLen));}break;case CHANNEL_TYPE_CAN:break;default:break;
      }
      
  • 通道通信接收数据处理

    • 基于通道类型,采用不同的处理方法将接收到的数据存放到接收缓存区中

      switch(m_pChannel->byChannelCOMType)   //通道类型
      {case CHANNEL_TYPE_TCP_CLIENT:case CHANNEL_TYPE_TCP_SERVER:case CHANNEL_TYPE_UDP_CLIENT:case CHANNEL_TYPE_UDP_SERVER:m_NewRecvedDataMutex.lock();while(m_sNewRecvedDataList.size() > 0){S_NewRecvedData& sNewRecvedData = m_sNewRecvedDataList.first();quint32 uiFreeRecvBufLen = MAX_BUF_SIZE - m_SPTLRecvSenddata.uiRecvedLen;   //剩余的接收缓冲区长度if(uiFreeRecvBufLen >= sNewRecvedData.uiRecvedLen)  //若剩余的缓冲区长度大于新接收的缓冲区长度,则新接收到的数据全部复制 {memcpy(m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen, sNewRecvedData.pRecvBuf, sNewRecvedData.uiRecvedLen);m_SPTLRecvSenddata.uiRecvedLen += sNewRecvedData.uiRecvedLen;m_sNewRecvedDataList.removeFirst();}else if(uiFreeRecvBufLen > 0)  //若剩余的接收缓冲区长度大于0,则只复制剩余的接收缓冲区长度{memcpy(m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen, sNewRecvedData.pRecvBuf, uiFreeRecvBufLen);m_SPTLRecvSenddata.uiRecvedLen += uiFreeRecvBufLen;sNewRecvedData.uiRecvedLen -= uiFreeRecvBufLen;memmove(sNewRecvedData.pRecvBuf, sNewRecvedData.pRecvBuf + uiFreeRecvBufLen, sNewRecvedData.uiRecvedLen);break;}else{break;}}m_NewRecvedDataMutex.unlock();break;case CHANNEL_TYPE_COM_485:case CHANNEL_TYPE_COM_232:m_uiMaxRecvLen = MAX_BUF_SIZE - m_SPTLRecvSenddata.uiRecvedLen;if(m_uiMaxRecvLen > 0){m_SPTLRecvSenddata.iRecvedLen = m_pSerial->recvData(m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen,static_cast<int>(m_uiMaxRecvLen));  //从串口中接收数据if(m_SPTLRecvSenddata.iRecvedLen > 0){m_tRecvedTime = time(NULL);addCommMsg(true, static_cast<quint16>(m_SPTLRecvSenddata.iRecvedLen), m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen);  //添加通信报文setUpCommFault(false);setDownCommFault(false);m_SPTLRecvSenddata.uiRecvedLen += static_cast<quint32>(m_SPTLRecvSenddata.iRecvedLen);}else if(m_SPTLRecvSenddata.iRecvedLen < 0){setCommState(COMM_ERROR);}}break;case CHANNEL_TYPE_CAN:break;default:break;
      }
      
    • 调用规约报文解析函数来解析报文

      m_pFuncParseData(m_SPTLRecvSenddata, dynamic_cast<CUnitBase*>(m_pCurrentUnit)
      
      PTL_API bool parseData(S_RecvSendData &sRecvSendData, CUnitBase* pUnit) //解析报文
      {S_PtlVariant sVariant;memset(&sVariant, 0, sizeof(S_PtlVariant));if(pUnit != NULL){sVariant.pUnit = pUnit;sVariant.pByReadBuffer = sRecvSendData.pRecvBuf;sVariant.uwRecvDataLen = sRecvSendData.uiRecvedLen;sVariant.bMainChannel = sRecvSendData.bMainChannel;sVariant.byChannelMode = sRecvSendData.byChannelMode;sVariant.byChannelIndex = sRecvSendData.byChannelIndex;memset(sVariant.pParsedCommMsg, 0, MAX_BUF_SIZE);sVariant.uwParsedCommMsgLen = 0;sVariant.pPtlFlag = (S_PtlFlag*)(pUnit->getTUFlagBuffer());if(readFrame(sVariant)) //解析数据{sRecvSendData.uiRecvedLen = sVariant.uwRecvDataLen;return true;}}sRecvSendData.uiRecvedLen = sVariant.uwRecvDataLen;return false;
      }
      

      根据要解析什么类型的报文,调用不同的解析函数

      • 解析查询到的数据,可细分为遥测、遥信、遥脉
      • 解析遥控命令的响应
      • 解析遥调命令的响应

      以解析查询到的数据为例,

      bool readFrame(S_PtlVariant &sVariant)   //解析数据
      {if(!sVariant.pPtlFlag->bPtlInitOK){if(initPtl(sVariant)){sVariant.pPtlFlag->bPtlInitOK = true;}else{return false;}}//判断接收长度  数据包长度if(sVariant.uwRecvDataLen < 6){return false;}if(FRAME_STATE_NO == sVariant.byCurrentRecvedFrameState){	//移除到接受数据中的MBAP头parseFrameStateNoToSyncing(sVariant);if(sVariant.byCurrentRecvedFrameState != FRAME_STATE_SYNING){return false;}}if(FRAME_STATE_SYNING == sVariant.byCurrentRecvedFrameState){parseFrameStateSyncingToSync(sVariant);if(sVariant.byCurrentRecvedFrameState != FRAME_STATE_SYN){return false;}}memcpy(sVariant.pParsedCommMsg + sVariant.uwParsedCommMsgLen, sVariant.pByReadBuffer, sVariant.uwCurrentRecvedFrameLen);sVariant.uwParsedCommMsgLen += sVariant.uwCurrentRecvedFrameLen;sVariant.pUnit->addCommMsg(true, sVariant.uwParsedCommMsgLen, sVariant.pParsedCommMsg, sVariant.byChannelMode, sVariant.byChannelIndex, sVariant.bMainChannel);//数据接收完毕,开始解析帧switch(sVariant.byCurrentRecvedFrameType){case FRAME_TYPE_DATA:praseQueryedData(sVariant);break;case FRAME_TYPE_CMD:if (sVariant.pUnit->getHaveCmd() && sVariant.pUnit->isCmdProcing()){switch(sVariant.pUnit->getCmdType()){case CMD_TYPE_YK:praseAllYK(sVariant);break;case CMD_TYPE_YT:parseAllYT(sVariant);break;default:break;}}break;default:break;}sVariant.byCurrentRecvedFrameState = FRAME_STATE_NO;sVariant.byCurrentRecvedFrameType = FRAME_TYPE_MAX;offsetRecvBuffer(sVariant, sVariant.uwCurrentRecvedFrameLen);sVariant.uwCurrentRecvedFrameLen = 0;sVariant.pPtlFlag->bWaitDataReturn = 0;return true;}void praseQueryedData(S_PtlVariant &sVariant)
      {//目前没考虑校验和switch(sVariant.pPtlFlag->byQueryType){case QUERYDATA_TYPE_YCDATA:if(0 == sVariant.pPtlFlag->byQueryYCNum ||sVariant.pPtlFlag->byQueryYCNum > static_cast<quint8>(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec.size())){return;}if(sVariant.pByReadBuffer[1] != sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec[sVariant.pPtlFlag->byQueryYCNum - 1]->byFUNCCODE){return;}praseYC(sVariant);break;case QUERYDATA_TYPE_YXDATA:if(0 == sVariant.pPtlFlag->byQueryYXNum ||sVariant.pPtlFlag->byQueryYXNum > static_cast<quint8>(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec.size())){return;}if (sVariant.pByReadBuffer[1] != sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec[sVariant.pPtlFlag->byQueryYXNum - 1]->byFUNCCODE){return;}praseYX(sVariant);break;case QUERYDATA_TYPE_YMDATA:if(0 == sVariant.pPtlFlag->byQueryYMNum ||sVariant.pPtlFlag->byQueryYMNum > static_cast<quint8>(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYMVec.size())){return;}if(sVariant.pByReadBuffer[1] != sVariant.pPtlFlag->pPtlModbus->pModbusQueryYMVec[sVariant.pPtlFlag->byQueryYMNum - 1]->byFUNCCODE){return;}praseYM(sVariant);break;default:break;}
      }
      

      解析查询帧响应时,需要对于不同的数据类型进行不同的处理,并在解析完成后设置数据库表中对应点位的值

      void praseYC(S_PtlVariant &sVariant)
      {const S_ModbusQuery* pModbusQuery = sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec[sVariant.pPtlFlag->byQueryYCNum -1];switch(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYCVec[sVariant.pPtlFlag->byQueryYCNum  - 1]->byDataType){case DATA_TYPE_UINT8:{double dYCValue = 0;quint8 bytempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint8);for(quint32 i = 0; i < uiCount; ++i){bytempYCValue = sVariant.pByReadBuffer[3 + i];dYCValue = double(bytempYCValue);sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_INT8:{double dYCValue = 0;qint8 bytempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(qint8);for(quint32 i = 0; i < uiCount; ++i){bytempYCValue = sVariant.pByReadBuffer[3 + i];dYCValue = double(bytempYCValue);sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_UINT16:{quint32 uiTUModelID = 0;char  chTUModelName[SIZE_NAME] = "";sVariant.pUnit->getValue(S_DataID(TABLE_TU, sVariant.pUnit->getTUID(), COL_TU_TUMODELID), &uiTUModelID);sVariant.pUnit->getValue(S_DataID(TABLE_TUMODEL, uiTUModelID, COL_TUMODEL_TUMODELNAME), chTUModelName);QString qsTUModelName = QString(chTUModelName);double dYCValue = 0;quint16 uwTempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint16);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uwTempYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(quint16), sizeof(quint16));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uwTempYCValue = qByteOrderConvert(uwTempYCValue);}uwTempYCValue = qBigLittleEndianConvert(uwTempYCValue);dYCValue = uwTempYCValue;if(qsTUModelName.contains("DLJTCW") && uwTempYCValue == 0xffff){qDebug()<<"值为:0XFFFF不上送";continue;}sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_INT16:{double dYCValue = 0;qint16 wTempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(qint16);for(quint32 i = 0; i < uiCount; ++i){memcpy(&wTempYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(qint16), sizeof(qint16));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){wTempYCValue = qByteOrderConvert(wTempYCValue);}wTempYCValue = qBigLittleEndianConvert(wTempYCValue);dYCValue = wTempYCValue;sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_UINT32:{double dYCValue = 0;quint32 uiTempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint32);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uiTempYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(quint32), sizeof(quint32));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uiTempYCValue = qByteOrderConvert(uiTempYCValue);}uiTempYCValue = qBigLittleEndianConvert(uiTempYCValue);dYCValue = uiTempYCValue;sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_INT32:{double dYCValue = 0;qint32 iTempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(qint32);for(quint32 i = 0; i < uiCount; ++i){memcpy(&iTempYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(qint32), sizeof(qint32));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){iTempYCValue = qByteOrderConvert(iTempYCValue);}iTempYCValue = qBigLittleEndianConvert(iTempYCValue);dYCValue = iTempYCValue;sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_FLOAT:{double dYCValue = 0;float fTempYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(float);for(quint32 i = 0; i < uiCount; ++i){memcpy(&fTempYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(float), sizeof(float));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){fTempYCValue = qByteOrderConvert(fTempYCValue);}fTempYCValue = qBigLittleEndianConvert(fTempYCValue);dYCValue = fTempYCValue;sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;case DATA_TYPE_DOUBLE:{double dYCValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(double);for(quint32 i = 0; i < uiCount; ++i){memcpy(&dYCValue, sVariant.pByReadBuffer + 3 + i * sizeof(double), sizeof(double));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){dYCValue = qByteOrderConvert(dYCValue);}dYCValue = qBigLittleEndianConvert(dYCValue);sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, pModbusQuery->uwStartINFO + i, COL_PREYC_YCVALUE), &dYCValue);}}break;default:break;}
      }
      

补充:

  • 数据类型如何影响报文帧
    在解析数据时,对不同的数据类型进行不同的解析处理
  • 字节序如何影响报文
    大端字节序通常被认为是“网络字节序”,若系统的硬件架构是按照小端存储的,则网络字节序需先转换成小端字节序再直接解析
  • 设备的点位由字节的一个位来决定(遥信点位)
    此时,Modbus查询帧表中数据类型为单字节(也可以是其他,由设备说明书决定),是否位偏移设置为是,
void praseYX(S_PtlVariant &sVariant)
{const S_ModbusQuery* pModbusQuery = sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec[sVariant.pPtlFlag->byQueryYXNum -1];bool bIfOffset = sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec[sVariant.pPtlFlag->byQueryYXNum -1]->bISOffset;quint32 uiIndex = 0;if(bIfOffset){switch(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec[sVariant.pPtlFlag->byQueryYXNum -1]->byDataType){case DATA_TYPE_UINT8:case DATA_TYPE_INT8:{quint8 bytempYXValue = 0;quint16 uwYXValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount;for(quint32 i = 0; i < uiCount; ++i){bytempYXValue = sVariant.pByReadBuffer[3 + i];for(quint32 j = 0; j < 8; ++j){uwYXValue = (bytempYXValue >> j) & 0x01;uiIndex = i * 8 + pModbusQuery->uwStartINFO + j;if(!sVariant.pUnit->isCommStateYX(uiIndex)) //不设置通信状态相关的遥信{sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, uiIndex, COL_PREYX_YXVALUE), &uwYXValue);}}}}break;case DATA_TYPE_UINT16:case DATA_TYPE_INT16:{quint16 uwTempYXValue = 0;quint16 uwYXValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint16);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uwTempYXValue, sVariant.pByReadBuffer + 3 + i * sizeof(quint16), sizeof(quint16));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uwTempYXValue = qByteOrderConvert(uwTempYXValue);}uwTempYXValue = qBigLittleEndianConvert(uwTempYXValue);for(quint32 j = 0; j < 16; ++j){uwYXValue = (uwTempYXValue >> j) & 0x01;uiIndex = i * 16 + pModbusQuery->uwStartINFO +  j;if(!sVariant.pUnit->isCommStateYX(uiIndex)) //不设置通信状态相关的遥信{sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, uiIndex, COL_PREYX_YXVALUE), &uwYXValue);}}}}break;case DATA_TYPE_UINT32:case DATA_TYPE_INT32:{quint32 uiTempYXValue = 0;quint16 uwYXValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint32);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uiTempYXValue, sVariant.pByReadBuffer + 3 + i * sizeof(quint32), sizeof(quint32));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uiTempYXValue = qByteOrderConvert(uiTempYXValue);}uiTempYXValue = qBigLittleEndianConvert(uiTempYXValue);for(quint32 j = 0; j < 32; ++j){uwYXValue = (uiTempYXValue >> j) & 0x01;uiIndex = i * 32 + pModbusQuery->uwStartINFO +  j;if(!sVariant.pUnit->isCommStateYX(uiIndex)) //不设置通信状态相关的遥信{sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, uiIndex, COL_PREYX_YXVALUE), &uwYXValue);}}}}break;default:break;}}else{switch(sVariant.pPtlFlag->pPtlModbus->pModbusQueryYXVec[sVariant.pPtlFlag->byQueryYXNum -1]->byDataType){case DATA_TYPE_UINT8:case DATA_TYPE_INT8:{quint16 uwYXValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount;for(quint32 i = 0; i < uiCount; ++i){uwYXValue = sVariant.pByReadBuffer[3 + i];uiIndex = pModbusQuery->uwStartINFO + i;if(!sVariant.pUnit->isCommStateYX(uiIndex)) //不设置通信状态相关的遥信{sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, uiIndex, COL_PREYX_YXVALUE), &uwYXValue);}}}break;case DATA_TYPE_UINT16:case DATA_TYPE_INT16:{quint16 uwYXValue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint16);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uwYXValue, sVariant.pByReadBuffer + 3 + i * sizeof(quint16), sizeof(quint16));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uwYXValue = qByteOrderConvert(uwYXValue);}sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, pModbusQuery->uwStartINFO + i, COL_PREYX_YXVALUE), &uwYXValue);}}break;case DATA_TYPE_UINT32:case DATA_TYPE_INT32:{quint32 uiYXvalue = 0;quint32 uiByteCount = sVariant.pByReadBuffer[2];quint32 uiCount = uiByteCount / sizeof(quint32);for(quint32 i = 0; i < uiCount; ++i){memcpy(&uiYXvalue, sVariant.pByReadBuffer + 3 + i * sizeof(quint32), sizeof(quint32));if(MODBUS_BYTEORDER_TYPE_H_L == pModbusQuery->byByteOrder){uiYXvalue = qByteOrderConvert(uiYXvalue);}sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYX, pModbusQuery->uwStartINFO + i, COL_PREYX_YXVALUE), &uiYXvalue);}}break;default:break;}}
}

参考文献

  • Modbus-3: Modbus TCP通信协议解析

  • modbus tcp 协议详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/14733.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鲲泰新闻丨第七届数字中国建设峰会正式启幕,神州鲲泰携手天翼云共筑智算云生态

2024年5月23日&#xff0c;由国家发展改革委、国家数据局、国家网信办、科技部、国务院国资委、福建省人民政府共同主办的“第七届数字中国建设峰会”在福建省福州市海峡国际会展中心盛大开幕。 数字中国建设峰会是展示数字中国建设成就的盛会&#xff0c;本次峰会以“释放数据…

【MYSQL】分数排名

表: Scores ---------------------- | Column Name | Type | ---------------------- | id | int | | score | decimal | ---------------------- id 是该表的主键&#xff08;有不同值的列&#xff09;。 该表的每一行都包含了一场比赛的分数。Score 是…

草图大师2024怎么保存低版本呢?插件怎么写?

草图大师是一款流行的绘图和设计软件&#xff0c;为了向后兼容&#xff0c;保存低版本文件时&#xff0c;可以采取以下步骤&#xff1a; su模型库 1.另存为旧版本格式&#xff1a; 在保存文件时&#xff0c;草图大师通常会提供一个选项&#xff0c;让你选择要保存的文件格式和…

智简云携手云器Lakehouse打造一体化大数据平台,释放数据价值

导读 本篇分享的是智简云使用云器Lakehouse升级数据平台的实践总结。 智简云&#xff0c;是一家拥有十余年历史的科技公司&#xff0c;专注于企业服务领域&#xff0c;开发了两款核心产品&#xff1a;基于PASS平台的客户关系管理&#xff08;CRM&#xff09;系统和为中小型用…

Go微服务——go-micro v4安装使用

安装go-micro 打开cmd窗口&#xff0c;执行以下命令 go install github.com/go-micro/cli/cmd/go-microlatest测试是否成功安装 go-micro -v创建服务 go-micro new service helloworldwindows 安装make 安装地址 https://gnuwin32.sourceforge.net/packages/make.htm 配置…

springboot集成达梦数据库8,用springboot+mtbatisplus查询值为空

springboot集成达梦数据库8&#xff0c;用springbootmtbatisplus查询值为空 背景&#xff1a;springboot集成达梦数据库8&#xff0c;用springbootmtbatisplus查询值为空&#xff0c;但是在DB管理工具中是可以查询到数据的。 原因及解决方法&#xff1a;执行添加语句后&#xf…

《MySQL怎样运行的》—InnoDB数据页结构

在上一篇文章中我们讲了&#xff0c;InnoDB的数据页是InnoDB管理存储空间的基本单位&#xff0c;一个页的大小基本为16kb 那你有没有疑问&#xff0c;就是说这个InnoDB的数据页的结构是什么样的&#xff0c;还有他这些结构分别有那些功能~接下来我们一一讲解 数据页的总览结构…

Linux笔记之命令行JSON处理器jq

Linux笔记之命令行JSON处理器jq code review! 文章目录 Linux笔记之命令行JSON处理器jq1.安装2.jq 基本用法3.例程3.1. 示例JSON文件3.2. 读取特定字段3.3. 管道过滤器&#xff08;Pipe Filters&#xff09;3.4. 映射过滤器&#xff08;Map Filters&#xff09;3.5. 条件过滤…

自动化重置数据库功能的探索与实践

1、简介 在现代软件开发中&#xff0c;尤其是涉及到数据驱动的应用程序时&#xff0c;开发和测试环境中数据库的管理是至关重要的一环。为了确保开发和测试环境中的数据库始终处于一致的状态&#xff0c;自动化重置数据库成为了一种常见的实践。本文旨在介绍如何通过Shell脚本…

内网(极空间)搭建gitlab跳板机转发端口及域名配置

背景说明 https://blog.csdn.net/GodDavide/article/details/139182475 上文说到: 我已经用docker搭好了gitlab-ce服务&#xff0c;但我是部署在自己的家庭nas-极空间z4pro里的&#xff0c;属于内网环境。 另外我有一台阿里云服务器&#xff0c;做跳板机。 我有一个阿里的域名…

老Java学 Go 笔录(二) 从 go 的编译开始学起

目录 一.版本选择二.环境准备三.工具的选择四.第一个 hello go4.1 开发4.2 编译4.3 编译运行4.4 直接安装 五.用 go 快速搭建 webserver六.调用外部三方方法七.go vs java 的执行 前言 专栏旨在利用现有的 java 体系内容去完成 go 语言的学习. 本次行文是在 https://go.dev/doc…

迈向2024年,投资无人售货机的盈利能力

在科技进步的浪潮下&#xff0c;无人值守自动售货商店已悄然引领了零售业的新风尚。但关于在2024年开设此类商店是否依旧能实现盈利&#xff0c;这需要一番深思熟虑的分析。 首先不可否认&#xff0c;无人售货机拥有多项显著优势。它打破了传统零售的时间与地点局限&#xff0…

[Linux] 进程概念

目录 1.冯诺依曼硬件体系结构 2.操作系统&#xff08;OS&#xff09; 3.系统接口 4.进程的概念 5.进程状态 6.四个其他概念 7.环境变量 8.进程地址空间 1.冯诺依曼硬件体系结构 在冯诺依曼体系结构中&#xff0c;计算机是由输入、输出、存储设备和中央处理器cpu组成的。图中体结…

Unity 自定义Web GL 发布模板

前言 使用讯飞语音识别时&#xff0c;发布Web GL 平台后需要在index.html 中添加相应的script 标签&#xff0c;但每次发布完添加比较麻烦&#xff0c;添加一个发布模板就可以不必每次发布完再手动添加修改。 实现 在Assets 文件夹下新建一个文件夹&#xff0c;重命名为WebG…

重构2:重构的原则之笔记

最近在看重构2&#xff1a;改善既有代码的设计这本书&#xff0c;对于代码重构指导非常有帮助&#xff0c;然后也是做个笔记记录下&#xff0c;以下是我阅读本书的前两章的时候整理的思维导图&#xff1a;

华为手机卡顿(仅针对于部分人来说,我也不清楚是否真的有用)

关机&#xff01; 之前一段时间手机变得特别卡顿&#xff0c;然后网上搜了一堆教程一点用没有&#xff0c;结果因为昨天下午在考试所以把手机关机了一个多小时&#xff0c;再打开之后手机就变得很流畅&#xff0c;原因不详&#xff0c;但效果显著&#xff0c;如有需要可尝试一…

docker 挂载运行镜像

文章目录 前言docker 挂载运行镜像1. 作用2. 命令3. 测试 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在白嫖的话&#xff0c;那欢…

软考 软件设计师 场景分析题 速成篇

文章目录 试题一&#xff1a;数据流图&#x1f496; 基本图形元素1. 外部实体2. 数据存储3. 加工4. 数据流 &#x1f4da; 例题&#xff08;1&#xff09;实体名称&#xff08;2&#xff09;数据存储名称&#xff08;3&#xff09;数据流① 父子图平衡② 加工有输入有输出④ 数…

在Windows10中重命名文件和文件夹的6种方法,有你熟悉和不熟悉的

序言 你可以通过多种方式在Windows 10上重命名文件。如果每次你想更改文件名时仍右键单击并选择“重命名”,那么我们有一些技巧可以加快更改速度。 使用文件资源管理器重命名文件和文件夹 Windows 10的文件资源管理器是一个功能强大的工具。你知道吗,有四种不同的方法可以…

CSS【常用CSS样式、盒子模型、定位、浮动 、扩展样式】--学习JavaEE的day46

day46 CSS 练习 页面实现&#xff1a; 分析&#xff1a; 未优化&#xff1a; 优化&#xff1a; 参考代码&#xff1a;&#xff08;包含样式优化–选择器CSS属性&#xff09; 先写上table方便实现&#xff0c;之后再去除即可 name没有服务器&#xff0c;可暂时不写 <!…