尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
qptp 时间同步流程
网络层级流程说明目前 gPTP 同步到 Android 有 vhal 同步和 ptpClock 同步两种方式平台化考虑 Tier1 自行决定 采用哪种方案。时间同步优先级gPTP NTP RTC。开机时安卓侧请求 RTC 时间此时没有 gPTP 时间则用 RTC 时间。Tbox 部署 gPTP master发起时间同步消息QNX 作为 gPTP slave 接收消息并同步系统时间 Android 和 QNX 时间同步更新 同步周期暂定为 1-10 秒之间。QNX 系统 gPTP 更新时间后时间同步服务校准 RTC 时间并通知 Android TimesyncService 校准Android 系统时间同时通知 CarTimeSync 已同步 gPTP 时间。如果未收到 CarTimeSync 已同步 gPTP 时间的通知并且已经获取到 NTP 时间由 CarTimeSync 通知QNX 侧同步 NTP 时间同时更新 Android 侧时间。如果后面 gPTP 时间同步成功直接使用gPTP时间覆盖同时更新 QNX 和 Android 侧时间。1.2时间同步的必要条件时间基准: 选取一个设备的时钟作为主时钟(类似于电视台或电包台的时钟),Grandmaster周期同步: 主时钟定期发布同步信息(类似于电视台或电台整点报时),如周期1s更新同步校准: 其他设备根据同步信息校正自己的本地时钟(相位同步,频率同步)1.3.3基于gPTP协议的时间同步方案基于以太网总线,使用时间同步协议实现时间同步PTP: (Precision Time Protocol,精确时间协议),IEEE 15883●gPTP: (General Precise Time Protocol,通用精确时间协议), IEEE 802.1ASgPTP(IEEE 802.1AS), 基于PTP (IEEE 1588v2)协议进行了一系列优化,形成了更具有针对性的时间同步机制, gPTP的目标是确保局域网里的所有设备的时间完全一致(同步精度在±500ns内),车载领域常用gPTP主端口发送 Sync 报文报文离开主端口 MAC 层时触发主端口记录此时的时间戳 T1。从端口接收 Sync 报文报文到达从端口 MAC 层时触发从端口记录此时的时间戳 T2。主端口再次发送 Sync 报文报文离开主端口 MAC 层时触发主端口记录此时的时间戳 T3。从端口接收 Sync 报文报文到达从端口 MAC 层时触发从端口记录此时的时间戳 T4。通过两组 Sync 报文和两组 Follow - UP 可以计算出各个节点之间的频率偏差。主时钟在 t1 时刻发送 Sync 命令从时钟在 t2 时刻收到同步指令。主时钟发送一个 Follow_Up 命令该命令中携带 t1 的值让从时钟知道 Sync 指令的发送时间。从时钟在 t3 时刻发送一个 Delay_Req 命令。主时钟在 t4 时刻收到该命令后发送一个 Delay_Resp 响应携带 t4 的值让从时钟知道主时钟收到 Delay_Req 命令的时间。从时钟根据 t1、t2、t3、t4 四个值计算路径传输延时和自己与主时钟的偏差进而调整自己的时间GPTPGeneralized Precision Time Protocol通用精确时间协议时间戳采样方式的 MAC 层实现需要从 GPTP 时间戳采样方式、MAC 层功能以及两者结合的意义和实现细节等方面来理解以下为你展开介绍GPTP 时间戳采样方式基础GPTP 是 IEEE 802.1AS 标准的一部分用于在以太网网络中实现高精度的时钟同步。时间戳采样方式是 GPTP 协议中用于记录数据包收发时间的方法有硬件时间戳、软件时间戳和混合时间戳三种常见方式。这些时间戳是计算时钟偏移和网络延迟的关键依据对于实现准确的时钟同步至关重要。MAC 层的功能和作用MACMedia Access Control介质访问控制层是数据链路层的下半部分主要负责控制与连接物理层的物理介质。它的主要功能包括帧封装与解封装将上层协议数据单元封装成 MAC 帧以及将接收到的 MAC 帧解封装为上层数据。介质访问控制决定节点何时可以访问共享介质避免数据冲突。物理地址识别使用 MAC 地址来识别网络中的不同设备。GPTP 时间戳采样方式在 MAC 层实现的含义时间戳生成位置在 MAC 层实现 GPTP 时间戳采样意味着时间戳的生成是在 MAC 层完成的。当数据包在 MAC 层进行封装或解封装时记录下精确的时间信息。例如在发送数据包时MAC 层在帧即将离开物理层接口的时刻生成发送时间戳在接收数据包时MAC 层在帧刚进入物理层接口的时刻生成接收时间戳。高精度时间同步MAC 层实现时间戳采样能够提供更精确的时间信息因为它更接近物理层减少了上层软件处理带来的延迟和不确定性。这对于需要高精度时钟同步的应用场景如工业自动化、电力系统中的分布式测量和控制非常重要。MAC 层实现的具体方式硬件支持许多网络接口控制器NIC或交换机芯片都提供了在 MAC 层实现时间戳采样的硬件支持。这些硬件可以在 MAC 层的特定事件如帧发送开始、帧接收完成发生时自动记录时间戳。硬件实现的优点是速度快、精度高能够达到纳秒级别的时间精度。软件辅助虽然硬件实现是主要方式但软件也可以在 MAC 层实现时间戳采样中发挥辅助作用。例如软件可以对硬件生成的时间戳进行校准和修正以补偿硬件时钟的漂移和误差。此外软件还可以处理一些复杂的时间戳计算和同步算法。实现步骤和流程硬件配置首先需要对支持 MAC 层时间戳采样的硬件设备进行配置包括设置时间戳生成的触发条件如帧发送、帧接收和时间戳的格式。配置硬件时钟源确保其具有足够的精度和稳定性。软件编程在操作系统或驱动程序中编写代码以读取和处理 MAC 层生成的时间戳。实现 GPTP 协议的消息交换和同步算法根据时间戳计算时钟偏移和网络延迟并调整本地时钟。同步过程主时钟周期性地发送 Sync 消息在 MAC 层记录发送时间戳。从时钟在 MAC 层接收到 Sync 消息时记录接收时间戳并发送 Delay_Req 消息同样在 MAC 层记录发送和接收时间戳。主时钟和从时钟通过交换这些时间戳信息计算网络延迟和时钟偏移实现时钟同步。验证项/* gPTP LPM modes */#define GPTP_LPM_MODE_OFF 1#define GPTP_LPM_MODE_ON 2qgptp emac1 -V -l 1 // 2SYNC_MESSAGE 0, DELAY_REQ_MESSAGE 1, PATH_DELAY_REQ_MESSAGE 2, PATH_DELAY_RESP_MESSAGE 3,else if (strcmp(argv[i] 1, V) 0) { portInit.automotive_profile true; } sigemptyset(set); sigaddset(set, SIGINT); sigaddset(set, SIGTERM ); sigaddset(set, SIGHUP); sigaddset(set, SIGUSR2); if (pthread_sigmask(SIG_BLOCK, set, NULL) ! 0) {/** * enum Event * IEEE 1588 event enumeration type * Defined at: IEEE 1588-2008 Clause 9.2.6 */typedef enum { NULL_EVENT 0, //! Null Event. Used to initialize events. POWERUP 5, //! Power Up. Initialize state machines. INITIALIZE, //! Same as POWERUP. LINKUP, //! Triggered when link comes up. LINKDOWN,//! Triggered when link goes down. STATE_CHANGE_EVENT, //! Signalizes that something has changed. Recalculates best master. SYNC_INTERVAL_TIMEOUT_EXPIRES, //! Sync interval expired. Its time to send a sync message. PDELAY_INTERVAL_TIMEOUT_EXPIRES, //! PDELAY interval expired. Its time to send pdelay_req message SYNC_RECEIPT_TIMEOUT_EXPIRES, //! Sync receipt timeout. Restart timers and take actions based on ports state. QUALIFICATION_TIMEOUT_EXPIRES, //! Qualification timeout. Event not currently used ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES, //! Announce receipt timeout. Same as SYNC_RECEIPT_TIMEOUT_EXPIRES ANNOUNCE_INTERVAL_TIMEOUT_EXPIRES, //! Announce interval timout. Its time to send an announce message if asCapable is true FAULT_DETECTED, //! A fault was detected. PDELAY_DEFERRED_PROCESSING, //! Defers pdelay processing PDELAY_RESP_RECEIPT_TIMEOUT_EXPIRES, //! Pdelay response message timeout PDELAY_RESP_PEER_MISBEHAVING_TIMEOUT_EXPIRES, //! Timeout for peer misbehaving. This even will re-enable the PDelay Requests SYNC_RATE_INTERVAL_TIMEOUT_EXPIRED, //! Sync rate signal timeout for the Automotive Profile POWERDOWN //! Power down. closing GPTP daemon, comes when ctrl c is used} Event;int main(int argc, char **argv)//daemon_cl.cpp pPort new EtherPort(portInit);if (!pPort-init_port()) {bool CommonPort::init_port( void ){ return _init_port(); EtherPort::_init_port( void ) EtherPort::EtherPort( PortInit_t *portInit ) : CommonPort( portInit )timestamper new LinuxTimestamperGeneric(); portInit.timestamper timestamper; //sequenceId and message type based int LinuxTimestamperGeneric::HWTimestamper_txtimestamp ( int32_t messageId, uint16_t sequenceId, Timestamp timestamp, unsigned clock_value, bool last ) PTPMessageCommon *buildPTPMessage void EtherPort::processMessage ( char *buf, int length, LinkLayerAddress *remote, uint32_t link_speed ) { GPTP_LOG_VERBOSE(Processing network buffer);PTPMessageCommon *msg buildPTPMessage( buf, (int)length, remote, this );if (pm_state ! 0) {if (pm_state GPTP_LPM_MODE_ON) {qgptp_port-gPTP_lpm true; qgptp_port-processEvent(LINKDOWN); }void PTPMessageSignalling::processMessage( EtherPort *port )bool PTPMessageFollowUp::sendPort bool CommonPort::processEvent( Event e ) //! Sync interval expired. Its time to send a sync message. case SYNC_INTERVAL_TIMEOUT_EXPIRES: //发起同步msg GPTP_LOG_DEBUG(SYNC_INTERVAL_TIMEOUT_EXPIRES occured);// If asCapable is true attempt some media specific action ret true;if( asCapable )ret _processEvent( e ); /* Do getDeviceTime() after transmitting sync frame causing an update to local/system timestamp */{ Timestamp system_time; Timestamp mono_time; Timestamp device_time; uint32_t local_clock, nominal_clock_rate; FrequencyRatio local_system_freq_offset; FrequencyRatio local_mono_freq_offset 0;int64_t local_system_offset; int64_t local_mono_offset;getDeviceTime ( system_time, mono_time, device_time, local_clock, nominal_clock_rate ); GPTP_LOG_VERBOSE ( port::processEvent(): System time: %u,%u Device Time: %u,%u, system_time.seconds_ls, system_time.nanoseconds, device_time.seconds_ls, device_time.nanoseconds ); local_system_offset TIMESTAMP_TO_NS(system_time) - TIMESTAMP_TO_NS(device_time); local_mono_offset TIMESTAMP_TO_NS(mono_time) - TIMESTAMP_TO_NS(device_time); local_system_freq_offset clock-calcLocalSystemClockRateDifference ( device_time, system_time, mono_time, local_mono_freq_offset );clock-setMasterOffset ( this, 0, device_time, 1.0, local_system_offset, system_time, local_system_freq_offset, local_mono_offset, mono_time, local_mono_freq_offset, getSyncCount(), pdelay_count, port_state, asCapable ); } // Call media specific action for completed sync syncDone();bool EtherPort::_processEvent( Event e )case SYNC_INTERVAL_TIMEOUT_EXPIRES: { /* Set offset from master to zero, update device vs system time offset */ // Send a sync message and then a followup to broadcast PTPMessageSync *sync new PTPMessageSync(this); PortIdentity dest_id; bool tx_succeed;getPortIdentity(dest_id);sync-setPortIdentity(dest_id); getTxLock();tx_succeed sync-sendPort(this, NULL);GPTP_LOG_VERBOSE(Sent SYNC message);bool PTPMessageSync::sendPort ( EtherPort *port, PortIdentity *destIdentity ) { uint8_t buf_t[256]; uint8_t *buf_ptr buf_t port-getPayloadOffset(); unsigned char tspec_msg_t 0x0; Timestamp originTimestamp_BE; uint32_t link_speed; memset(buf_t, 0, 256); // Create packet in buf // Copy in common header messageLength PTP_COMMON_HDR_LENGTH PTP_SYNC_LENGTH; tspec_msg_t | messageType 0xF; buildCommonHeader(buf_ptr); // Get timestamp originTimestamp port-getClock()-getTime(); originTimestamp_BE.seconds_ms PLAT_htons(originTimestamp.seconds_ms); originTimestamp_BE.seconds_ls PLAT_htonl(originTimestamp.seconds_ls); originTimestamp_BE.nanoseconds PLAT_htonl(originTimestamp.nanoseconds); // Copy in v2 sync specific fields memcpy(buf_ptr PTP_SYNC_SEC_MS(PTP_SYNC_OFFSET), (originTimestamp_BE.seconds_ms), sizeof(originTimestamp.seconds_ms)); memcpy(buf_ptr PTP_SYNC_SEC_LS(PTP_SYNC_OFFSET), (originTimestamp_BE.seconds_ls), sizeof(originTimestamp.seconds_ls)); memcpy(buf_ptr PTP_SYNC_NSEC(PTP_SYNC_OFFSET), (originTimestamp_BE.nanoseconds), sizeof(originTimestamp.nanoseconds)); port-sendEventPort ( PTP_ETHERTYPE, buf_t, messageLength, MCAST_OTHER, destIdentity, link_speed ); port-incCounter_ieee8021AsPortStatTxSyncCount(); return getTxTimestamp( port, link_speed ); }时间同步过程主时钟周期性地发送包含其当前时间戳的同步消息Sync Message从时钟在接收到同步消息时记录本地时间戳。然后从时钟通过与主时钟交换延迟请求Delay_Req Message和延迟响应Delay_Resp Message消息测量主从时钟之间的消息传输延迟。根据这些时间戳和传输延迟信息从时钟可以计算出与主时钟的时间偏差并据此调整本地时钟实现时间同步。void IEEE1588Clock::setMasterOffset ( CommonPort *port, int64_t master_local_offset, #define INVALID_TIMESTAMP (Timestamp( 0xC0000000, 0, 0 )) /*! Defines an invalid timestamp using a Timestamp instance and a fixed value*/ #define PDELAY_PENDING_TIMESTAMP (Timestamp( 0xC0000001, 0, 0 )) /*! PDelay is pending timestamp */ PTPMessageCommon *buildPTPMessage ( char *buf, int size, LinkLayerAddress *remote, EtherPort *port ) { if (port NULL) { return NULL; } OSTimer *timer port-getTimerFactory()-createTimer(); PTPMessageCommon *msg NULL; msg-_timestamp timestamp; class PTPMessageSync : public PTPMessageCommon 父类实现 void PTPMessageFollowUp::processMessage( EtherPort *port ) { GPTP_LOG_DEBUG(Processing a follow-up message); PTPMessageSync *sync port-getLastSync(); sync_arrival sync-getTimestamp(); local_clock_adjustment port-getClock()- calcMasterLocalClockRateDifference ( preciseOriginTimestamp, sync_arrival ); scalar_offset TIMESTAMP_TO_NS( sync_arrival ); scalar_offset - TIMESTAMP_TO_NS( preciseOriginTimestamp ); port-getClock()-setMasterOffset ( port, scalar_offset, sync_arrival, local_clock_adjustment,/* brief API to get rx time stamp value.\\ details This function will read received packets timestamp from\\ the descriptor and pass it to stack and also perform some sanity checks.\param[in] pdata - pointer to private data structure.\param[in] pmbuf - pointer to mbuf structure.\param[in] desc_data - pointer to wrapper receive descriptor structure.\param[in] q_indx - Queue/Channel number.*/static unsigned charemac_get_rx_hwtstamphw_if-get_rx_tstamp emac_get_rx_tstamp;/*!\brief This sequence is used get the 64-bit of the timestampcaptured by the device for the corresponding received packetin nanosecond.\param[in] rxdesc\return (unsigned long long) on success\retval ns/**/static ULONG_LONGemac_get_rx_tstamp( emac_rx_cntxt_desc_t *rxdesc){ULONG_LONG ns;ULONG varrdes1;/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/RX_CONTEXT_DESC_RDES0_Ml_Rd(rxdesc-RDES0, ns);RX_CONTEXT_DESC_RDES1_Ml_Rd(rxdesc-RDES1, varrdes1);ns ns (varrdes1 * 1000000000ull);return ns;} /* emac_get_rx_tstamp */static unsigned charemac_get_rx_hwtstamppdata-xstats.rx_timestamp_captured_n;/* get valid tstamp */ns hw_if-get_rx_tstamp((struct emac_rx_cntxt_desc_s *)rx_context_desc);int emac_ptp_is_eventmsg ( struct emac_prv_data_t *pdata, struct mbuf *m, ptpv2hdr_t **ph ) { int ret EMAC_FALSE; int remain, pktlen; struct ether_header *eh; struct ip *iph; struct udphdr *udph; emac_dev_t *dev pdata-parent; if (m NULL) { DBGPR(%s: NULL mbuf, __func__); goto check_failed; } if (m-m_len sizeof(struct ether_header) sizeof(ptpv2hdr_t)) { goto check_failed; } eh (struct ether_header *) m-m_data; switch (ntohs(eh-ether_type)) { case ETHERTYPE_PTP:if (emac_ptp_is_eventmsg(pdata, pmbuf, ph) TRUE ph ! NULL) { if (pmbuf-m_len sizeof(struct ether_header) sizeof(ptpv2hdr_t)) { EMAC_MSG_ERROR(%s: length of packet is less than ether_header ptpv2hdr : pmbuf-m_len:%d, __func__, pmbuf-m_len); return EMAC_PTP_PKT_INVAL; } pthread_mutex_lock(pdata-ptp_rx_mutex); pdata-rx_ptp_event[pdata-ptp_event_rx_idx].seqID ntohs(ph-sequenceId); pdata-rx_ptp_event[pdata-ptp_event_rx_idx].messageType ph-messageId 0x0F; pdata-rx_ptp_event[pdata-ptp_event_rx_idx].ts ns;DBGPR(%s: PTP Rx msgType: %d seqId: %d ts %llu,__func__, pdata-rx_ptp_event[pdata-ptp_event_rx_idx].messageType, pdata-rx_ptp_event[pdata-ptp_event_rx_idx].seqID, pdata-rx_ptp_event[pdata-ptp_event_rx_idx].ts); pdata-ptp_event_rx_idx (pdata-ptp_event_rx_idx 1) % EMAC_PTP_EVENT_BUF_SIZE; pthread_mutex_unlock(pdata-ptp_rx_mutex); }switch (pulse.code) { case EMAC_RX_HANDLE_PULSE: emac_disable_all_ch_rx_interrpt(pdata); while (1 ! pdata-threads_need_quiesce 1 ! pdata-rx_thread_need_quiesce) { received emac_rx_poll_mq(emac_dev, weight); for (chInx 1; chInx EMAC_RX_QUEUE_CNT; chInx) { per_q_received pdata-clean_rx(emac_dev-pdata, per_q_budget, chInx); received per_q_received; } else { pdata-rx_buffer_len EMAC_ETH_FRAME_LEN; pdata-clean_rxemac_clean_rx_irq; pdata-alloc_rx_buf emac_alloc_rx_buf; EMAC_MSG_DEBUG1(alloc_rx_buf emac_alloc_rx_buf); } static intemac_clean_rx_irq/* get rx tstamp if available */ if (hw_if-rx_tstamp_available(rx_normal_desc)) { DBGPR(--%s: RX timestamp is available q_indx %u,__func__, q_indx); ret emac_get_rx_hwtstamp(pdata, pmbuf, desc_data, q_indx); if (ret 0)上层调用的接口o configure the gPTP clock manually as a timeTransmitter clock, run the following command: qgptp eth0 -V -GM ■ To configure the gPTP clock manually as a timeReceiver clock, run the following command: qgptp eth0 -V 你给出的内容是关于手动配置 gPTPGeneric Precision Time Protocol通用精确时间协议时钟的说明。下面为你详细解释这些命令以及 gPTP 时钟的相关概念。gPTP 时钟简介gPTP 是一种用于在以太网网络中实现高精度时间同步的协议。在一个 gPTP 网络里时钟设备可以分为不同的角色主要有时间发送者timeTransmitter和时间接收者timeReceiver。时间发送者timeTransmitter也被称作主时钟Grandmaster Clock它会产生精确的时间信号并通过网络将这些时间信息发送给其他设备。时间接收者timeReceiver也叫从时钟Slave Clock它会接收主时钟发送的时间信号并根据这些信号来调整自己的时钟以实现与主时钟的时间同步。命令解释qgptp eth0 -V -GM qgptp这是一个用于配置和管理 gPTP 时钟的工具。eth0指定要使用的网络接口。在这个例子中使用的是名为eth0的以太网接口。-V通常是用于启用详细日志输出的选项。开启该选项后qgptp工具会输出更详细的调试信息方便用户了解 gPTP 时钟的配置和运行情况。-GM表示将该设备配置为时间发送者主时钟。GM是 Grandmaster 的缩写。这是一个 shell 命令符号用于将命令放到后台执行。这样在执行命令后终端可以继续接受其他输入而不必等待qgptp命令执行完毕。qgptp eth0 -V 这个命令与上面的命令类似区别在于没有-GM选项。在没有-GM选项的情况下qgptp工具会将该设备配置为时间接收者从时钟。因此该命令的作用是将eth0接口对应的设备配置为 gPTP 时间接收者并开启详细日志输出命令在后台执行。在广义精确时间同步协议gPTP中时钟同步的 One - Step一步法和 Two - Step两步法是两种不同的时间戳传递方式用于实现主从时钟之间的时间同步2。具体如下2One - Step一步法主时钟在同步消息中直接发送其当前的时间戳。从时钟接收到消息后就会根据这个时间戳来更新自己的时钟。这种方法的优点是简单处理速度快但是因为处理时间和网络传输时间的不确定性可能会影响到时间同步的精度。Two - Step两步法主时钟首先发送一个同步消息但这个消息中并不包含时间戳而是标记为两步法的标志。然后主时钟在发送完同步消息后再发送一个关联的后续消息这个消息中包含了发送同步消息时的精确时间戳。从时钟在接收到这两个消息后就可以根据这个精确的时间戳来更新自己的时钟。两步法虽然比一步法复杂需要发送和处理更多的消息但是因为它能够提供更精确的时间戳所以能够提供更高的时间同步精度。
RELATED

相关推荐

车道识别模型---Unet环境部署和个人数据集训练教程

车道识别模型---Unet环境部署和个人数据集训练教程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/28 9:25:40
端侧 AI 选运行时:先看模型覆盖,再看 WebGPU

端侧 AI 选运行时:先看模型覆盖,再看 WebGPU

页面能申请 GPUDevice,说明获得了一种计算能力,不代表目标模型已经可运行。模型格式、算子、精度、预处理和资源预算仍可能让推理失败。 2026 年 10 月 19 日,Google 介绍 LiteRT Tensor API:用原生 C 逐算子构建图,提…

📅 2026/10/28 9:25:40
TongWeb8.0生产环境配置规范之一:安装环境配置

TongWeb8.0生产环境配置规范之一:安装环境配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/28 9:25:40
MORE NEWS

更多资讯

📰

我的电视(my-tv):免注册、开箱即用的电视直播软件,把老电视变成直播入口

我的电视(my-tv):免注册、开箱即用的电视直播软件,把老电视变成直播入口 【免费下载链接】my-tv 我的电视 电视直播软件,安装即可使用 项目地址: https://gitcode.com/GitHub_Trending/my/my-tv 家里只想看央视…

📰

为什么你的断点续传总是从 0 开始:Hutool 大文件下载避坑实战

为什么你的断点续传总是从 0 开始:Hutool 大文件下载避坑实战 【免费下载链接】hutool 🍬A set of tools that keep Java sweet. 项目地址: https://gitcode.com/gh_mirrors/hu/hutool 用 Hutool 做文件下载,几 MB 的包一行 HttpUtil.…

📰

Codex 免配置一键安装包|本地部署不用敲一行命令

前言 做开发的朋友应该都有体会,想要本地部署 Codex,最麻烦的不是软件本身,而是前期环境准备。手动安装 Node.js、处理依赖版本冲突,还要打开终端输入多条命令,稍有不慎就会报错,很多新手卡在这一步直接放…

📰

HarmonyOS 通知点开只进首页?让通知直接跳到指定业务页面【鸿蒙心迹】

👋 你好,欢迎来到我的博客!我是【菜鸟学鸿蒙】我是一名在路上的移动端开发者,正从传统“小码农”转向鸿蒙原生开发的进阶之旅。为了把学习过的知识沉淀下来,也为了和更多同路人互相启发,我决定把探索 Harmo…

📰

my-tv:免费电视直播软件,电视盒子装上就能看

my-tv:免费电视直播软件,电视盒子装上就能看 【免费下载链接】my-tv 我的电视 电视直播软件,安装即可使用 项目地址: https://gitcode.com/GitHub_Trending/my/my-tv my-tv 是一款开源的电视直播软件,免注册、不收费&#…

📰

【HarmonyOS 7新能力|008】互动卡片入门实战:从能力边界到最小可运行链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬