当前位置: 首页 > article >正文

【PtpBasics】- KRTS C++示例精讲(7)

PtpBasics示例讲解


目录

    • PtpBasics示例讲解
      • 结构说明
      • 代码说明


项目打开请查看【BaseFunction精讲】。

结构说明

在这里插入图片描述
PtpBasics.cpp:用户应用层源码

  • 其余文件说明请查看【BaseFunction精讲】中的结构说明。
    ps : 内核层中的数据、结构体需要一字节对齐,需要以MT方式构建
    在这里插入图片描述

代码说明

/* Copyright (c) 2017-2024 by Kithara Software GmbH. All rights reserved. */

//##############################################################################################################
//
// 文件: 	  	PtpBasics.cpp
//
// 模块:	 	PTP 模块, 时钟模块
//
// 描述: 	    示例应用程序显示如何设置 PTP 时间同步
//
// 创建人:      r.gro 2017-09-04
//
//##############################################################################################################

   /*=====================================================================*\
   |                    *** 免责声明 ***                     			   |
   |                                                                       |
   |       本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任!		   |
   |																	   |
   \*=====================================================================*/

//##############################################################################################################
//
// 目的:
//
// 本示例应用程序展示了如何使用 PTP 模块设置 PTP 时间同步。
//
// 在示例中,将为主时钟创建一个 PTP 时钟实例。
// 然后,可以添加一个或多个 PTP 端口。
// 最后,将循环显示 PTP 时间,并显示 PTP 端口和时钟的状态变化。
//
// 在通过以太网连接的两台 PC 上运行示例,可以启用自动时间同步功能。
// 可以启用自动时间同步。
//
//##############################################################################################################

#include "../_KitharaSmp/_KitharaSmp.h"


//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// 别忘了输入你的序列号(6位客户编号),这是打开驱动程序所必需的。
// 
// 如果你使用Demo版本,也可以使用“DEMO”代替。
// 如果你使用Beta版本,也可以使用“BETA”代替。
//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

// 如上说所,定义的客户号 
const char _pCustomerNumber[] = "DEMO";


//--------------------------------------------------------------------------------------------------------------
// CallBackData 结构的定义,用于与回调共享数据,作为其引用参数。
//--------------------------------------------------------------------------------------------------------------

struct CallBackData {
  KSHandle hClock;
  KSHandle* phPort;
  int portCount;
  int running;
};


//--------------------------------------------------------------------------------------------------------------
// PTP事件回调,该文件末尾定义的回调函数的声明。
//--------------------------------------------------------------------------------------------------------------

KSError __stdcall ptpEventCallBack(void* pArgs, void* /*pContext*/);

// 主程序入口
void runSample() {
  outputTxt("***** Kithara example program 'PtpBasics' *****");
  
  // 错误码定义,KSError 是 Kithara API 所有函数的返回类型,通过 【KSError】 可以查询接口的返回错误信息。
  KSError ksError;

  //------------------------------------------------------------------------------------------------------------
  // 打开驱动程序的第一步,所有KRTS程序必须进行的操作。
  // 只要该函数调用成功后,我们可以使用其他函数。如果打开失败,则无法调用其他函数。
  // 此函数接受您的客户编号作为参数,其中包含 Kithara(如果适用可以为“DEMO”或“BETA”)。
  //------------------------------------------------------------------------------------------------------------
  
  ksError = KS_openDriver(
              _pCustomerNumber);                        // 客户序列号
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_openDriver", "Unable to open the driver!");
    return;
  }

  //------------------------------------------------------------------------------------------------------------
  // 创建并初始化传给回调的 CallBackData 实例。
  //------------------------------------------------------------------------------------------------------------

  CallBackData referenceParameter;
  referenceParameter.running = 0;


  //------------------------------------------------------------------------------------------------------------
  // 创建回调以处理 PTP 事件。
  //------------------------------------------------------------------------------------------------------------

  Handle hCallBack;
  ksError = KS_createCallBack(
              &hCallBack,                               // 返回回调句柄
              ptpEventCallBack,                         // 回调函数
              &referenceParameter,                      // 参考参数传递给回调函数
              KSF_USER_EXEC,                            // 在应用层中执行
              0);                                       // 优先级值未使用
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_createCallBack", "Unable to create user callback!");
    KS_closeDriver();
    return;
  }


  //------------------------------------------------------------------------------------------------------------
  // 为主时钟创建时钟实例。
  //------------------------------------------------------------------------------------------------------------

  KSPtpClockConfig clockConfig = { 0 };
  clockConfig.structSize = sizeof(KSPtpClockConfig);
  clockConfig.mode       = inputDec("PTP mode (1 = BMCA / 2 = Grandmaster / 3 = Slave-only): ",
      KS_PTP_BEST_MASTER_CLOCK_ALGORITHM);

  const short int defaultPriority = 128;

  clockConfig.priority1 = inputDec("Priority 1 of Best Master Clock Algorithm: ", defaultPriority);
  clockConfig.priority2 = inputDec("Priority 2 of Best Master Clock Algorithm: ", defaultPriority);

  KSHandle hClock;
  ksError = KS_createPtpClock(
              &hClock,                                  // 返回 PTP 时钟句柄
              KS_INVALID_HANDLE,                        // 设备句柄(可选)
              &clockConfig,                             // KSPtpClockConfig结构的地址
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_createPtpClock", "Failed to create PTP clock instance!");
    KS_closeDriver();
    return;
  }

  referenceParameter.hClock = hClock;


  //------------------------------------------------------------------------------------------------------------
  // 安装一个 PTP 处理程序,如果 PTP 主节点发生变化,将发出信号。
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_installPtpHandler(
              hClock,                                   // PTP 句柄
              KS_PTP_MASTER_SELECTED,                   // 事件代码
              hCallBack,                                // 回调句柄
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_installPtpHandler", "Failed to install PTP handler!");
    KS_closeDriver();
    return;
  }


  //------------------------------------------------------------------------------------------------------------
  // 添加一个或多个 PTP 端口.
  //------------------------------------------------------------------------------------------------------------

  int portCount = inputDec("Number of PTP ports: ", 1);
  referenceParameter.portCount = portCount;

  referenceParameter.phPort = new KSHandle[portCount];
  KSHandle* phAdapter       = new KSHandle[portCount];
  KSHandle* phVlan          = new KSHandle[portCount];

  KSPtpPortConfig portConfig;
  portConfig.structSize           = sizeof(KSPtpPortConfig);
  portConfig.announceInterval     = 1;
  portConfig.syncInterval         = 0;
  portConfig.delayRequestInterval = 0;

  KSNetworkAdapterConfig adapterConfig = { 0 };
  adapterConfig.structSize  = sizeof(KSNetworkAdapterConfig);  // 不要忘记初始化结构体大小
  adapterConfig.recvPoolLength = 500;
  adapterConfig.sendPoolLength = 500;
  adapterConfig.configFlags = KS_NETWORK_ACCEPT_ALL | KS_NETWORK_ENABLE_PTP;

  KSNetworkVlanConfig vlanConfig;
  vlanConfig.structSize     = sizeof(KSNetworkVlanConfig);
  vlanConfig.priority       = 7;
  vlanConfig.dropEligible   = 0;
  vlanConfig.recvPoolLength = 500;
  vlanConfig.sendPoolLength = 500;

  KSIPConfig ipConfig;
  KS_makeIPv4(&ipConfig.subnetMask,     255, 255, 255, 0);
  KS_makeIPv4(&ipConfig.gatewayAddress, 192, 168,   0, 1);

  outputTxt(" ");

  for (int i = 0; i < portCount; ++i) {
    outputTxt(" ");
    outputDec(i, "Port: ");

    outputTxt("PTP port mode (1 = Standard(IEEE 1588) / 2 = gPTP(IEEE 802.1AS) / 3 = gPTP static-slave): ",
      false);
    portConfig.mode = inputDec("", KS_PTP_STANDARD_MODE);

    if (portConfig.mode == KS_PTP_GPTP_MODE || portConfig.mode == KS_PTP_GPTP_STATIC_SLAVE_MODE) {
      portConfig.transportLayer = KS_PTP_ETHERNET;
      portConfig.delayMechanism = KS_PTP_DELAY_PEER;
    }
    else {
      portConfig.transportLayer = inputDec("PTP transport layer (1 = Ethernet / 2 = IP-UDP): ",
        KS_PTP_ETHERNET);
      portConfig.delayMechanism = inputDec("PTP delay mechanism (1 = AUTO / 2 = ENDPOINT / 3 = PEER): ",
        KS_PTP_DELAY_ENDPOINT);
    }


    //----------------------------------------------------------------------------------------------------------
    // 选择 PTP 端口正在工作的网络适配器。
    //----------------------------------------------------------------------------------------------------------

    char pDeviceName[256];

    outputTxt(" ");
    outputTxt("Following network adapters found:");

    for (int i = 0; ; ++i) {

	// KS_enumDevices()可以用于查询分配给Kithara驱动程序的所有网络适配器的名称。
    ksError = KS_enumDevices(
                "NET",                                  // 'NET' 代表搜索网络设备
                i,                                      // 从0开始的枚举索引号
                pDeviceName,                            // 返回设备名称
                KSF_NO_FLAGS);                          // 无标记

      if (ksError != KS_OK) {
        if (KSERROR_CODE(ksError) != KSERROR_DEVICE_NOT_FOUND)
          outputErr(ksError, "KS_enumDevices", "Unable to query network device name!");
        if (KSERROR_CODE(ksError) == KSERROR_DEVICE_NOT_FOUND && !i) {
          outputTxt("No network adapters found!");
          outputTxt(" ");
          KS_closeDriver();
          return;
        }
        break;
      }
      outputDec(i, "", ": ", false);
      outputTxt(pDeviceName);
    }
    outputTxt(" ");


    //----------------------------------------------------------------------------------------------------------
    //  输入并保存索引号并根据索引和再次获取设备名称
    //----------------------------------------------------------------------------------------------------------

    int deviceIndex = inputDec("Device number: ", 0);


    //----------------------------------------------------------------------------------------------------------
    // 通过获取所选设备的名称来验证选择。
    //----------------------------------------------------------------------------------------------------------

 	 ksError = KS_enumDevices(
           	   "NET",                                    // 'NET' 代表搜索网络设备
           	   deviceIndex,                              // 选择的设备索引号
           	   pDeviceName,                              // 返回设备名称
           	   KSF_NO_FLAGS);                            // 无标记
    if (ksError != KS_OK) {
      outputErr(ksError, "KS_enumDevices", "Unable to query selected network device name!");
      KS_closeDriver();
      return;
    }

    outputTxt("Selected device: ", false);
    outputTxt(pDeviceName);


    //----------------------------------------------------------------------------------------------------------
    // 打开网络适配器。
    // 注意:KSNetworkAdapterConfig.configFlags 必须包含 KS_NETWORK_ENABLE_PTP
    // 以启用硬件时间戳!
    //----------------------------------------------------------------------------------------------------------

    ksError = KS_openNetworkAdapter(
                &phAdapter[i],                          // 返回适配器句柄
                pDeviceName,                            // 设备名称
                &adapterConfig,                         // KSNetworkAdapterConfig结构的地址
                KSF_NO_FLAGS);                          // 无标记
    if (ksError != KS_OK) {
      outputErr(ksError, "KS_openNetworkAdapter", "Failed to open adapter!");
      KS_closeDriver();
      return;
    }

    int vlanId = inputDec("VLAN-ID (-1 = VLAN not used): ", -1);

    phVlan[i] = KS_INVALID_HANDLE;

    if (vlanId != -1) {
      if (vlanId < 0 || vlanId > 4095) {
        outputTxt("VLAN-ID invalid");
        KS_closeDriver();
        return;
      }

      vlanConfig.id = vlanId;

      ksError = KS_openNetworkVlan(
                  &phVlan[i],                           // 返回 VLAN 处理程序句柄
                  phAdapter[i],                         // 适配器句柄
                  &vlanConfig,                          // KSNetworkVlanConfig结构的地址
                  KSF_NO_FLAGS);                        // 无标记
      if (ksError != KS_OK) {
        outputErr(ksError, "KS_openNetworkVlan", "Failed to create vlan!");
        KS_closeDriver();
        return;
      }
    }


    //----------------------------------------------------------------------------------------------------------
    // 如果 PTP 端口通过 IP/UDP 工作,我们必须设置 IP 配置。
    //----------------------------------------------------------------------------------------------------------

    KSHandle hArgHandle = phVlan[i] != KS_INVALID_HANDLE ? phVlan[i] : phAdapter[i];
    if (portConfig.transportLayer == KS_PTP_UDP) {


      //--------------------------------------------------------------------------------------------------------
      // 从用户那里获取IP地址的低字节。
      //--------------------------------------------------------------------------------------------------------

      int lowByte = inputDec("IP-address 192.168.0.x: ", 181);
      KS_makeIPv4(&ipConfig.localAddress, 192, 168, 0, lowByte);

      ksError = KS_execNetworkCommand(
                  hArgHandle,                           // 适配器句柄
                  KS_NETWORK_SET_IP_CONFIG,             // 命令:进行网络IP配置
                  &ipConfig,                            // 配置信息
                  KSF_NO_FLAGS);                        // 无标记
      if (ksError != KS_OK) {
        outputErr(ksError, "KS_execNetworkCommand", "Failed to set IP config!");
        KS_closeDriver();
        return;
      }
    }


    //----------------------------------------------------------------------------------------------------------
    // 将 PTP 端口添加到时钟实例。
    //----------------------------------------------------------------------------------------------------------

    ksError = KS_addPtpPort(
                hClock,                                 // PTP 时钟句柄
                &referenceParameter.phPort[i],          // 返回 PTP 端口句柄
                hArgHandle,                             // 连接句柄
                &portConfig,                            // 端口配置
                KSF_NO_FLAGS);                          // 无标记
    if (ksError != KS_OK) {
      outputErr(ksError, "KS_addPtpPort", "Failed to add PTP port!");
      KS_closeDriver();
      return;
    }


    //----------------------------------------------------------------------------------------------------------
    // 安装 PTP 处理程序,当端口状态发生变化时发出信号。
    //----------------------------------------------------------------------------------------------------------

    ksError = KS_installPtpHandler(
                referenceParameter.phPort[i],           // PTP 句柄
                KS_PTP_PORT_STATE_CHANGED,              // 事件代码:ptp 端口状态已更改
                hCallBack,                              // 事件回调
                KSF_NO_FLAGS);                          // 无标记
    if (ksError != KS_OK) {
      outputErr(ksError, "KS_installPtpHandler", "Failed to install PTP handler!");
      KS_closeDriver();
      return;
    }
  }

  outputTxt(" ");
  outputTxt(" ");
  outputTxt("Key commands: [L]ock clock, [U]nlock clock, [Q]uit");
  outputTxt(" ");


  //------------------------------------------------------------------------------------------------------------
  // 运行一次回调,打印起始状态。
  //------------------------------------------------------------------------------------------------------------

  referenceParameter.running = 1;

  ksError = KS_execCallBack(
              hCallBack,                                // 回调句柄
              NULL,                                     // 指向上下文的指针(未使用)
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_execCallBack", "Failed to execute callback!");
    KS_closeDriver();
    return;
  }


  //------------------------------------------------------------------------------------------------------------
  // 这是主循环,用于打印 PTP 时间。
  // 按 “L ”键,主时钟将被锁定,以避免绝对时间的跳变。
  // 按'U'键解锁。
  //------------------------------------------------------------------------------------------------------------

  bool isRunning = true;
  while (isRunning) {
    waitTime(100 * ms);

    int64 time;
    KS_getClock(
      &time,                                            // 返回时钟值
      KS_CLOCK_PTP_TIME);                               // 时钟类型

    outputDec(time, "PTP time: ", "\r", false);

    if (myKbhit()) {
      switch (myGetch()) {
        case 'q':
        case 'Q':
          isRunning = false;
          break;

        case 'l':
        case 'L':
          ksError = KS_lockPtpClock(
                      hClock,                           // 时钟句柄
                      KSF_NO_FLAGS);                    // 无标记
          if (ksError != KS_OK)
            outputErr(ksError, "KS_lockPtpClock", "Failed to lock PTP clock!");
          else {
            outputTxt(" ");
            outputTxt(" ");
            outputTxt("Master clock locked.");
            outputTxt(" ");
          }
          break;

        case 'u':
        case 'U':
          ksError = KS_unlockPtpClock(
                      hClock,                           // 时钟句柄
                      KSF_NO_FLAGS);                    // 无标记
          if (ksError != KS_OK)
            outputErr(ksError, "KS_unlockPtpClock", "Failed to unlock PTP clock!");
          else {
            outputTxt(" ");
            outputTxt(" ");
            outputTxt("Master clock unlocked.");
            outputTxt(" ");
          }
          break;
      }
    }
  }


  //------------------------------------------------------------------------------------------------------------
  // 关闭 PTP 端口。
  //------------------------------------------------------------------------------------------------------------

  for (int i = 0; i < portCount; ++i) {
    ksError = KS_closePtp(
                referenceParameter.phPort[i],           // 时钟端口句柄
                KSF_NO_FLAGS);                          // 无标记
    if (ksError != KS_OK)
      outputErr(ksError, "KS_closePtp", "Unable to close PTP port!");
  }


  //------------------------------------------------------------------------------------------------------------
  // 关闭PTP时钟.
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_closePtp(
              hClock,                                   // PTP时钟句柄
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK)
    outputErr(ksError, "KS_closePtp", "Unable to close PTP clock!");


  //------------------------------------------------------------------------------------------------------------
  // 关闭网络适配器
  //------------------------------------------------------------------------------------------------------------

  for (int i = 0; i < portCount; ++i) {
    if (phVlan[i] != KS_INVALID_HANDLE) {
      ksError = KS_closeNetwork(
                  phVlan[i],                            // VLAN网络句柄
                  KSF_NO_FLAGS);                        // 无标记
    }
    if (ksError != KS_OK)
      outputErr(ksError, "KS_closeNetwork", "Unable to close vlan network adapter!");

    ksError = KS_closeNetwork(
                phAdapter[i],                           // 网络适配器句柄
                KSF_NO_FLAGS);                          // 无标记
    if (ksError != KS_OK)
      outputErr(ksError, "KS_closeNetwork", "Unable to close network adapter!");
  }

  delete [] referenceParameter.phPort;
  delete [] phAdapter;
  delete [] phVlan;


  //------------------------------------------------------------------------------------------------------------
  // 移除回调
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_removeCallBack(
              hCallBack);                               // 回调句柄
  if (ksError != KS_OK)
    outputErr(ksError, "KS_removeCallBack", "Failed to remove callback!");


  //------------------------------------------------------------------------------------------------------------
  // 关闭驱动程序以释放任何分配的资源。
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_closeDriver();
  if (ksError != KS_OK)
    outputErr(ksError, "KS_closeDriver", "Unable to close the driver!");

  waitTime(500 * ms);
  outputTxt(" ");
  outputTxt("End of program 'PtpBasics'.");
}


//--------------------------------------------------------------------------------------------------------------
// PTP 事件处理程序的回调函数。
// 查询并显示 PTP 时钟和端口的状态。
//--------------------------------------------------------------------------------------------------------------

KSError __stdcall ptpEventCallBack(void* pArgs, void* /*pContext*/) {
  KSError ksError;
  CallBackData* pData = (CallBackData*)pArgs;

  if (pData->running == 0)
    return KS_OK;

  KSPtpClockState clockState;
  clockState.structSize = sizeof(KSPtpClockState);      // 不要忘记初始化结构体大小
  KSPtpPortState  portState;
  portState.structSize  = sizeof(KSPtpPortState);       // 不要忘记初始化结构体大小


  ksError = KS_getPtpClockState(
              pData->hClock,                            // PTP 时钟句柄
              &clockState,                              // 返回时钟状态
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_getPtpClockState", "Failed to get PTP clock state!");
    return ksError;
  }

  outputTxt(" ");
  outputTxt("clockId:              ", false);

  for (int i = 0; i < 8; ++i)
    outputHex02(clockState.clockIdentity.identity[i], "", "", false);

  outputTxt(" ");
  outputTxt("parentId:             ", false);

  for (int i = 0; i < 8; ++i)
    outputHex02(clockState.parentPortIdentity.identity.identity[i], "", "", false);

  outputDec(clockState.parentPortIdentity.portNumber, ":");

  outputTxt("grandMasterId:        ", false);

  for (int i = 0; i < 8; ++i)
    outputHex02(clockState.grandMasterIdentity.identity[i], "", "", false);

  outputTxt(" ");
  outputDec(clockState.stepsFromGrandMaster, "stepsFromGrandMaster: ");
  outputDec(clockState.utcOffset, "utcOffset: ");

  for (int i = 0; i < pData->portCount; ++i) {
    ksError = KS_getPtpPortState(
                pData->phPort[i],                       // PTP 端口句柄
                &portState,                             // 返回端口状态
                KSF_NO_FLAGS);                          // 无标记
    if (ksError != KS_OK) {
      outputErr(ksError, "KS_getPtpPortState", "Failed to get PTP port state!");
      return ksError;
    }

    outputDec(i, "Port ", ": ", false);
    outputTxt("state: ", false);
    switch(portState.state) {
      case 0:                         outputTxt("NONE"); break;
      case KS_PTP_STATE_INITIALIZING: outputTxt("INITIALIZING"); break;
      case KS_PTP_STATE_FAULTY:       outputTxt("FAULTY"); break;
      case KS_PTP_STATE_DISABLED:     outputTxt("DISABLED"); break;
      case KS_PTP_STATE_LISTENING:    outputTxt("LISTENING"); break;
      case KS_PTP_STATE_PRE_MASTER:   outputTxt("PRE_MASTER"); break;
      case KS_PTP_STATE_MASTER:       outputTxt("MASTER"); break;
      case KS_PTP_STATE_PASSIVE:      outputTxt("PASSIVE"); break;
      case KS_PTP_STATE_UNCALIBRATED: outputTxt("UNCALIBRATED"); break;
      case KS_PTP_STATE_SLAVE:        outputTxt("SLAVE"); break;
      default:                        outputTxt("UNKNOWN"); break;
   }

    if (portState.state == KS_PTP_STATE_FAULTY) {
      outputTxt("Faulty state detected - trying to reset...");
      ksError = KS_execPtpCommand(
                  pData->phPort[i],                     // PTP 句柄
                  KS_PTP_CLEAR_FAULTS,                  // 命令: ptp 清除故障
                  NULL,                                 // 参数
                  KSF_NO_FLAGS);                        // 无标记
      if (ksError != KS_OK)
        outputErr(ksError, "KS_execPtpCommand", "Failed to execute reset command!");
    }
  }

  outputTxt(" ");
  return KS_OK;
}


http://www.kler.cn/a/470433.html

相关文章:

  • 如何轻松反转C# List<T>中的元素顺序
  • 微服务-Eureka
  • 支付宝手机网站支付
  • 前端学习DAY31(子元素溢出父元素)
  • 资源分享:gpts、kaggle、paperswithcode
  • LLM - 使用 LLaMA-Factory 部署大模型 HTTP 多模态服务 教程 (4)
  • docker中使用Volume完成数据共享
  • ESP32物联网无线方案,智能穿戴设备联网通信,产品无线交互应用
  • 从入门到精通:Ansible Shell 模块的应用与最佳实践
  • 智慧工地解决方案深度解析:统一架构平台,十大产品线与AI+智慧工地产品趋势
  • 微服务中任务失败后如何进行重试
  • 嵌入式硬件设计的基本流程
  • 从git分支获取一个新项目
  • Swift Concurrency(并发)学习
  • 在环境冲突情况下调整优先级以解决ROS Catkin构建中缺少模块的问题【ubuntu20.04】
  • Outlook 网页版一直提示:检测到重复的重定向
  • Selenium 的四种等待方式及使用场景
  • C# 检查一个字符串是否是科学计数法格式字符串 如 1.229266E+01
  • Requests聚焦爬虫-数据解析
  • 项目48:简易语言学习助手【源代码】 --- 《跟着小王学Python·新手》
  • 线程的创建与管理:Java的多重身份
  • 【React】刷新页面或跳转路由时进行二次确认
  • 【问题记录】SpringBoot 解决 getReader() has already been called for this request 错误
  • F#语言的计算机基础
  • HTML - <link>
  • 03、MySQL安全管理和特性解析(DBA运维专用)