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

news2024/12/26 11:44:04

文章目录

  • 一、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 协议详解

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

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

相关文章

C++质数的那些事(判断指数、区间筛质数、互质等等)

质数的定义&#xff1a;若一个正整数除了1和它自身之外不能被任何自然数整除&#xff0c;则该数称为质数&#xff0c;也叫素数。否则为合数。 质数的性质&#xff1a;质数的分布较为稀疏&#xff0c;对于一个足够大的数S&#xff0c;不超过S的质数大约有个&#xff0c;也就是说…

视频安防监控EasyCVR视频汇聚管理平台视频播放花屏的原因分析及处理

智慧安防监控EasyCVR视频管理平台能在复杂的网络环境中&#xff0c;将前端设备统一集中接入与汇聚管理。国标GB28181协议视频监控/视频汇聚EasyCVR平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、…

基于SpringBoot和Hutool工具包实现的验证码案例

目录 验证码案例 1. 需求 2. 准备工作 3. 约定前后端交互接口 需求分析 接口定义 4. Hutool 工具介绍 5. 实现验证码 后端代码 前端代码 6. 运行测试 验证码案例 随着安全性的要求越来越高&#xff0c;目前项目中很多都会使用验证码&#xff0c;只要涉及到登录&…

【C语言】VS编译器的scanf

我们在写代码的时候通常需要用到输入函数&#xff1a;scanf&#xff0c;但在vs编译环境下却必须写为&#xff1a;scanf_s&#xff0c;这是为什么呢&#xff1f;这里就是vs规定的了&#xff0c;VS认为这样写更安全&#xff0c;但如果我们非要写成scanf形式也是有办法的。 # 看我…

hls.js实现分片播放视频

前言&#xff1a;hls.js官网&#xff1a;hls.js - npm 一、demo——在HTML中使用 <audio id"audio" controls></audio><script src"https://cdn.jsdelivr.net/npm/hls.jslatest"></script> <script>document.addEventList…

OTP8脚-全自动擦鞋机WTN6020-低成本语音方案

一&#xff0c;产品开发背景 首先&#xff0c;随着人们生活质量的提升&#xff0c;对鞋子的保养需求也日益增加。鞋子作为人们日常穿着的重要组成部分&#xff0c;其清洁度和外观状态直接影响到个人形象和舒适度。因此&#xff0c;一种能够自动清洁和擦亮鞋子的设备应运而生&am…

oracle中insert all的用法

1、简述 使用insert into语句进行表数据行的插入&#xff0c;但是oracle中有一个更好的实现方式&#xff1a;使用insert all语句。 insert all语句是oracle中用于批量写数据的 。insert all分又为 无判断条件插入有判断条件插入有判断条件插入分为 Insert all when... 子句 …

VMware 和 VirtualBox开机自启指定虚拟机详细教程

VMware上虚拟机随宿主机开机自启 1. 设置自动启动虚拟机 网上教程旧版的&#xff0c;界面和新版有所差异。17版本设置如下&#xff1a;VMware Workstation工作台 -> 文件 -> 配置自动启动虚拟机 -> 按顺序选择需要启动的虚拟机 VMWare17配置自动启动虚拟机提示&…

使用WindTerm通过公钥让其他同事登录linux服务器

1.其他同事电脑操作cmd输入 ssh-keygen,如果是第一次生成公钥&#xff0c;则直接一路回车确认就行 会生成在C:\Users\nideNAME\.ssh目录 公钥文件名为id_rsa.pub&#xff0c;私钥文件名为id_rsa。 2.你的电脑你已经连接上服务器了 在root下新建目录 root/.ssh/新建文件touck …

数据库小项目——叮叮移动业务大厅(三层架构+MySQL数据库)

源码已上传至资源 该项目主要使用技术为MySQL数据库&#xff0c;其中也包含了一些对于文件的写入和读取操作。项目结构采用三层架构&#xff0c;后端的业务逻辑清晰明了。 1.项目结构 项目采用控制台版&#xff0c;前端业务在java包下&#xff0c;每个业务单独成块。若想要GUI…

GD32F307+lwip+freeRTOS+DP83848 JPerf接收测速

1.原理图 2.代码 https://www.firebbs.cn/forum.php?modviewthread&tid26274&fromuid37393 //22_ENET 1&#xff09;注释掉tcp_client_init(); 2&#xff09;init_task中添加测速线程iperf_server_init() //main.c #include "gd32f30x.h" #include &quo…

自定义RedisTemplate序列化器

大纲 RedisSerializerFastJsonRedisSerializer自定义二进制序列化器总结代码 在《RedisTemplate保存二进制数据的方法》一文中&#xff0c;我们将Java对象通过《使用java.io库序列化Java对象》中介绍的方法转换为二进制数组&#xff0c;然后保存到Redis中。实际可以通过定制Red…

QTextCodec NO such file or directory让qt6兼容qt5

首先在.pro 文件中新加 QT core5compat这时会报错 链接 报错之后修复qt&#xff0c;新加兼容模块&#xff0c;见链接。

C++实现基于http协议的epoll非阻塞模型的web服务器框架(支持访问服务器目录下文件的解析)

使用方法&#xff1a; 编译 例子&#xff1a;./httpserver 9999 ../ htmltest/ 可执行文件 端口 要访问的目录下的 例子&#xff1a;http://192.168.88.130:9999/luffy.html 前提概要 http协议 &#xff1a;应用层协议&#xff0c;用于网络通信&#xff0c;封装要传输的数据&…

LeetCode 79.单词搜索

原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内…

本地centos7+docker+ollama+gpu部署

1、一台有 NVIDIA GPU 驱动的机器 2、Docker CE安装 # 删除旧版本的 Docker&#xff08;如果存在&#xff09; sudo yum remove -y docker docker-common docker-selinux docker-engine # 安装必要的软件包&#xff1a; sudo yum install -y yum-utils device-mapper-persiste…

PyCharm设置——用于调试虚拟环境中的django程序

使用Pycharm新建了一个项目。 项目目录&#xff1a;C:\Users\grace\PycharmProjects\learning_log 在该路径下安装虚拟环境ll_env&#xff0c;并在虚拟环境下安装Django。 为了调试该Django需要对PyCharm进行设置。 1、确保PyCharm使用正确的虚拟环境 打开PyCharm&#xff…

市面上前 11 名的 Android 数据恢复软件

Android数据恢复软件是恢复无意中删除的文件或文件夹的必要工具。该软件还将帮助您恢复丢失或损坏的信息。本文介绍提供数据备份和磁盘克隆选项的程序&#xff0c;这些选项有助于在Android设备上恢复文件的过程。 如果您正在寻找一种有效的方法来恢复图像&#xff0c;文档&…

qmt量化交易策略小白学习笔记第8期【qmt编程之获取股票资金流向数据--内置Python】

qmt编程之获取股票资金流向数据 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 感谢关注&#xff0c;需免费开通量化回测与咨询实盘权限&#xff0c;可以和博主联系&#xff01; 获取股票资金…

java学习四

Random 随机数 数组 静态初始化数组 数组在计算机中的基本原理 数组的访问 什么是遍历 数组的动态初始化 动态初始化数组元素默认值规则 Java内存分配介绍 数组在计算机中的执行原理 使用数组时常见的一个问题 案例求数组元素最大值 public class Test1 {public static void ma…