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;  //加载规约的lib
    
    char 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;
      
          //寄存器起始地址HL
          quint16 uwRegStartAddr = pModbusQuery->uwStartAddr;
          if(MODBUS_BYTEORDER_TYPE_H_L == sVariant.pPtlFlag->pPtlModbus->pModbus->byRegisterByteOrder)
          {
              uwRegStartAddr =  qByteOrderConvert(uwRegStartAddr);
      
          }
          memcpy(pBySendBuffer + 2, &uwRegStartAddr, 2);
      
          //寄存器数量HL
          quint16 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 debug
                      qMSleep(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 协议详解

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

富甲美国---沃尔玛创始人山姆·沃尔顿

富甲美国---沃尔玛创始人山姆沃尔顿_山姆沃尔顿是犹太人吗?-CSDN博客文章浏览阅读786次。​1.不断地检讨回顾我们做得好不好或需要改进的&#xff0c;我们从没有对现况满足过。我们会短暂地大肆庆祝成功&#xff0c;然后认真地检讨下次如何能做得更好---不断改进与创新。2我们…

LSTM实例解析

大家好&#xff0c;这里是七七&#xff0c;今天带给大家的实例解析。以前也用过几次LSTM模型&#xff0c;但由于原理不是很清楚&#xff0c;因此不能清晰地表达出来&#xff0c;这次用LSTM的时候&#xff0c;去自习研究了原理以及代码&#xff0c;来分享给大家此次经历。 一、简…

【漏洞复现】海康威视综合安防管理平台 iSecure Center applyCT fastjson 远程代码执行

0x01 漏洞名称 海康威视综合安防管理平台 iSecure Center applyCT fastjson 远程代码执行 0x02 漏洞影响 0x03 搜索引擎 app"HIKVISION-综合安防管理平台"0x04 漏洞详情 POST /bic/ssoService/v1/applyCT HTTP/1.1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Wi…

【30天精通Prometheus:一站式监控实战指南】第6天:mysqld_exporter从入门到实战:安装、配置详解与生产环境搭建指南,超详细

亲爱的读者们&#x1f44b;   欢迎加入【30天精通Prometheus】专栏&#xff01;&#x1f4da; 在这里&#xff0c;我们将探索Prometheus的强大功能&#xff0c;并将其应用于实际监控中。这个专栏都将为你提供宝贵的实战经验。&#x1f680;   Prometheus是云原生和DevOps的…

【Linux】网络层——IP协议

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;Linux 目录 &#x1f449;&#x1f3fb;IP协议基本概念&#x1f449;&#x1f3fb;IP的协议头格式&#x1f449;&#x1f3fb;IP协议的网段划分五类IP地址子…

大数据技术原理(一):还记得你大数据入门的第一份测试题吗

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

真实案例分享,终端pc直接telnet不到出口路由器。

1、背景信息 我终端pc的网卡地址获取的网关是在核心交换机上&#xff0c;在核心交换机上telnet出口路由器可以实现。 所有终端网段都不能telnet出口路由器&#xff0c;客户希望能用最小的影响方式进行解决。 2、现有配置信息 终端的无线和有线分别在两个网段中&#xff0c;…

c++设计模式-->访问者模式

#include <iostream> #include <string> #include <memory> using namespace std;class AbstractMember; // 前向声明// 行为基类 class AbstractAction { public:virtual void maleDoing(AbstractMember* member) 0;virtual void femaleDoing(AbstractMemb…

【Linux】套接字的理解 基于TCP协议的套接字编程(单/多进程 / 线程池|英汉互译 / C++)

文章目录 1. 前言1.1 网络方面的预备知识&#x1f447;1.2 了解TCP协议 2. 关于套接字编程2.1 什么是套接字 Socket2.2 socket 的接口函数2.3 Udp套接字编程的步骤2.4 sockaddr 结构 3. 代码实现3.1 makefile3.2 log.hpp3.3 tcp_server.hpp① 框架② service() 通信服务③ init…

Linux安装刻录软件

在工作场景经常使用光盘和刻录机&#xff0c;在windows系统下有nero软件&#xff0c;在linux下有k3b,但是原始的k3b只能一次刻录&#xff0c;十分浪费光盘&#xff0c;这里我们使用经优麒麟优化过的刻录软件&#xff0c;实现多次追加刻录。 进入优麒麟软件仓库&#xff0c;需要…

CentOS7.9安装mysql-8.0.36踩坑小记

前言&#xff1a; 最近想在一台测试服务器上&#xff0c;安装下最新的MySQL 8.0 版本数据库&#xff0c;想着挺简单的一件事&#xff0c;之前也手搓过 8.0 安装&#xff0c;这不手到擒来&#xff0c;没想到马失前蹄&#xff0c;遇到了一个小坑&#xff0c;耗费了不少时间&…

docker 安装RabbitMQ-web版本

直接拉去web版本 docker pull rabbitmq:management启动命令 设置用户名 admin 密码123456 docker run -dit --name rabbitmq -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USERadmin -e RABBITMQ_DEFAULT_PASS123456 rabbitmq:management访问地址 http://127.0.0.1:…

AIGC实践|探索用AI实现小游戏开发全流程

前言&#xff1a; 在上一篇中&#xff0c;我们已经深入探讨了AI工具在动态绘本创作中的应用&#xff0c;体验了AI在创意内容生成上的魅力。本篇文章将带领大家进入一个全新的探索领域——游戏开发。 本文将详细介绍如何利用AI工具探索实现游戏开发的全过程&#xff0c;从概念…

第三期【数据库主题文档上传激励活动】已开启!快来上传文档赢奖励

2023年9月、11月&#xff0c;墨天轮社区相继举办了第一期与第二期【数据库主题文档上传激励活动】&#xff0c;众多用户积极参与、上传了大量优质的数据库主题干货文档&#xff0c;在记录经验的同时也为其他从业者带来了参考帮助&#xff0c;这正实现了“乐知乐享、共同成长”的…

存储+调优:存储-Cloud

存储调优&#xff1a;存储-Cloud Master Server 配置&#xff1a; IP192.168.1.254 useradd mfs tar zxf mfs-1.6.11.tar.gz.gz cd mfs-1.6.11 ./configure --prefix/usr --sysconfdir/etc --localstatedir/var/lib --with-default-usermfs --with-default-groupmfs --disabl…

CDN管理平台安装说明

CDN管理平台安装说明 系统需求 操作系统&#xff1a;Linux CPU不少于1核心 可用内存不少于1G 可用硬盘空间不小于10G 对于每日千万访问以上的CDN系统推荐配置如下&#xff1a; CPU不少于8核心 可用内存不少于8G 可用硬盘空间不小于200G 准备工作 在安装GoEdge之前&#xff0…

ClickHouse 几年内数据查询及细节

在 ClickHouse 中&#xff0c;查询三年内的时间数据可以使用以下方法&#xff1a; 1. 使用日期函数 可以使用 ClickHouse 支持的日期函数来筛选出三年内的数据。例如&#xff0c;使用 today() 函数获取当天日期&#xff0c;使用 toDate() 函数将日期转换为指定格式&#xff0…

前端更改线上请求地址

由于后台接口更改 , 线上请求地址需从 /api/api/ 改成 /api/ , 需实现的效果如下图 1 在原本的vite.config.js中将前端做的端口转发内容更改 , 更改一行即可 import { defineConfig } from vite import react from vitejs/plugin-react import path from path import * as fs …

520主题趣味小游戏玩法线上互动的作用是什么

行业商家借势520气氛&#xff0c;往往能低成本达到预期效果&#xff0c;包括但不限于品牌传播、渠道引流涨粉、用户促活引导等&#xff0c;除了前面推荐的互动玩法外&#xff0c;在【雨科】平台的这几款520趣味小游戏同样值得关注。 1、爱你不止520 这是一款九宫格抽奖活动&am…

浅谈后端整合Springboot框架后操作基础配置

boot基础配置 现在不访问端口8080 可以吗 我们在默认启动的时候访问的是端口号8080 基于属性配置的 现在boot整合导致Tomcat服务器的配置文件没了 我们怎么去修改Tomcat服务器的配置信息呢 配置文件中的配置信息是很多很多的... 复制工程 保留工程的基础结构 抹掉原始…