PTP协议精讲(4.2):消息结构与编码——PTP报文的“DNA“

4.2 消息结构与编码:PTP报文的"DNA"

源码版本说明

本章源码片段基于 ptp-lite v1.0.0 (2026-04-10)

⚠️ 重要提示

  • 完整、最新的源码请查看 ptp_lite/ 目录
  • 本章代码片段仅用于理解原理,可能不是最新版本
  • 如发现差异,以源码为准
  • 源码变更记录请查看 CHANGELOG.md

主要源码文件


消息格式总览

PTP消息有严格的二进制格式,必须精确编码:

复制代码
所有消息都以消息头开始:
┌─────────────────────────────────┐
│ Header (34 bytes)               │
└─────────────────────────────────┘

不同消息类型有不同的消息体:

Sync消息(44字节):
┌──────────┬──────────────────────┐
│ Header   │ Origin Timestamp(10B)│
└──────────┴──────────────────────┘

Follow_Up消息(44字节):
┌──────────┬───────────────────────────┐
│ Header   │ Precise Origin Timestamp  │
└──────────┴───────────────────────────┘

Delay_Req消息(44字节):
┌──────────┬──────────────────────┐
│ Header   │ Origin Timestamp(10B)│
└──────────┴──────────────────────┘

Delay_Resp消息(54字节):
┌──────────┬─────────────────┬───────────────┐
│ Header   │ Receive Timestamp│ Requesting ID │
└──────────┴─────────────────┴───────────────┘

Announce消息(64字节):
┌──────────┬─────────────┬────────────┬─────────┐
│ Header   │ Origin TS   │ UTC Offset │ Priority│
└──────────┴─────────────┴────────────┴─────────┘

公共定义

ptp_common.h

c 复制代码
/**
 * @file ptp_common.h
 * @brief PTP公共定义和类型
 * 
 * 轻量级PTP实现 - 教学版本
 * 支持E2E延迟测量、UDP/IPv4传输、软件时间戳
 * 
 * @version 1.0.0
 * @date 2026-04-10
 */

#ifndef PTP_COMMON_H
#define PTP_COMMON_H

#include <stdint.h>
#include <time.h>
#include <endian.h>

/* 版本信息 */
#define PTP_LITE_VERSION "1.0.0"
#define PTP_LITE_VERSION_DATE "2026-04-10"

/* PTP时间戳:48位秒 + 32位纳秒 */
typedef struct {
    uint16_t seconds_msb;    /* 秒的高16位 */
    uint32_t seconds_lsb;    /* 秒的低32位 */
    uint32_t nanoseconds;    /* 纳秒部分 */
} __attribute__((packed)) ptp_timestamp_t;

/* 时间间隔:64位纳秒 */
typedef int64_t ptp_timeinterval_t;

/* 时钟ID:8字节 */
typedef uint8_t ptp_clock_identity_t[8];

/* 端口ID */
typedef struct {
    ptp_clock_identity_t clock_identity;
    uint16_t port_number;
} __attribute__((packed)) ptp_port_identity_t;

/* 消息类型 */
#define PTP_MSG_SYNC           0x0
#define PTP_MSG_DELAY_REQ      0x1
#define PTP_MSG_FOLLOW_UP      0x8
#define PTP_MSG_DELAY_RESP     0x9
#define PTP_MSG_ANNOUNCE       0xB

/* PTP版本 */
#define PTP_VERSION            2

/* 组播地址和端口 */
#define PTP_PRIMARY_MCAST      "224.0.1.129"
#define PTP_EVENT_PORT         319
#define PTP_GENERAL_PORT       320

/* 默认参数 */
#define PTP_DEFAULT_DOMAIN     0
#define PTP_DEFAULT_PRIORITY1  128
#define PTP_DEFAULT_PRIORITY2  128
#define PTP_DEFAULT_ANNOUNCE_INT  1
#define PTP_DEFAULT_SYNC_INT      0

/* 辅助函数:timespec转PTP时间戳 */
static inline void timespec_to_ptp(const struct timespec *ts,
                                   ptp_timestamp_t *ptp)
{
    uint64_t sec = (uint64_t)ts->tv_sec;
    ptp->seconds_msb = htobe16((uint16_t)(sec >> 32));
    ptp->seconds_lsb = htobe32((uint32_t)(sec & 0xFFFFFFFFULL));
    ptp->nanoseconds = htobe32((uint32_t)ts->tv_nsec);
}

/* 辅助函数:PTP时间戳转timespec */
static inline void ptp_to_timespec(const ptp_timestamp_t *ptp,
                                   struct timespec *ts)
{
    ts->tv_sec = ((uint64_t)be16toh(ptp->seconds_msb) << 32) |
                 be32toh(ptp->seconds_lsb);
    ts->tv_nsec = be32toh(ptp->nanoseconds);
}

#endif /* PTP_COMMON_H */

实现要点

  1. 时间戳格式

    • 48位秒 + 32位纳秒
    • 符合IEEE 1588-2019标准
    • 使用 __attribute__((packed)) 避免内存对齐
  2. 字节序转换

    • PTP使用大端序(网络字节序)
    • 必须使用 htobe16htobe32 等函数
    • 接收时使用 be16tohbe32toh
  3. 类型安全

    • 使用显式类型转换,避免编译警告
    • 特别是 uint64_tuint16_t/uint32_t 的转换

消息结构定义

ptp_message.h

c 复制代码
/**
 * @file ptp_message.h
 * @brief PTP消息结构定义 - IEEE 1588-2019
 */

#ifndef PTP_MESSAGE_H
#define PTP_MESSAGE_H

#include "ptp_common.h"

/* PTP消息头 - IEEE 1588-2019 Figure 35 (34 bytes) */
typedef struct {
    uint8_t  message_type;              /* Octet 0: 消息类型 */
    uint8_t  version_ptp;               /* Octet 1: PTP版本 */
    uint16_t message_length;            /* Octets 2-3: 消息长度 */
    uint8_t  domain_number;             /* Octet 4: 域号 */
    uint8_t  reserved1;                 /* Octet 5: 保留 */
    uint16_t flag_field;                /* Octets 6-7: 标志字段 */
    uint64_t correction_field;          /* Octets 8-15: 校正字段 */
    uint32_t reserved2;                 /* Octets 16-19: 保留 */
    ptp_port_identity_t source_port_identity;  /* Octets 20-29: 源端口ID */
    uint16_t sequence_id;               /* Octets 30-31: 序列号 */
    uint8_t  control_field;             /* Octet 32: 控制字段(legacy) */
    int8_t   log_message_interval;      /* Octet 33: 消息间隔 */
} __attribute__((packed)) ptp_header_t;

/* Sync消息 (44 bytes) - IEEE 1588-2019 Figure 39 */
typedef struct {
    ptp_header_t header;                /* 34 bytes */
    ptp_timestamp_t origin_timestamp;   /* 10 bytes */
} __attribute__((packed)) ptp_sync_msg_t;

/* Follow_Up消息 (44 bytes) - IEEE 1588-2019 Figure 40 */
typedef struct {
    ptp_header_t header;                      /* 34 bytes */
    ptp_timestamp_t precise_origin_timestamp; /* 10 bytes */
} __attribute__((packed)) ptp_follow_up_msg_t;

/* Delay_Req消息 (44 bytes) - IEEE 1588-2019 Figure 41 */
typedef struct {
    ptp_header_t header;                /* 34 bytes */
    ptp_timestamp_t origin_timestamp;   /* 10 bytes */
} __attribute__((packed)) ptp_delay_req_msg_t;

/* Delay_Resp消息 (54 bytes) - IEEE 1588-2019 Figure 42 */
typedef struct {
    ptp_header_t header;                /* 34 bytes */
    ptp_timestamp_t receive_timestamp;  /* 10 bytes */
    ptp_port_identity_t requesting_port_identity; /* 10 bytes */
} __attribute__((packed)) ptp_delay_resp_msg_t;

/* Clock Quality结构 (4 bytes) - IEEE 1588-2019 7.6.3.3 */
typedef struct {
    uint8_t  clock_class;                    /* Octet 0: 时钟等级 */
    uint8_t  clock_accuracy;                 /* Octet 1: 时钟精度 */
    uint16_t offset_scaled_log_variance;     /* Octets 2-3: 偏差缩放 */
} __attribute__((packed)) ptp_clock_quality_t;

/* Announce消息 (64 bytes) - IEEE 1588-2019 Figure 43 */
typedef struct {
    ptp_header_t header;                     /* 34 bytes */
    ptp_timestamp_t origin_timestamp;        /* 10 bytes */
    uint16_t current_utc_offset;             /* 2 bytes */
    uint8_t  reserved1;                      /* 1 byte */
    uint8_t  grandmaster_priority1;          /* 1 byte */
    ptp_clock_identity_t grandmaster_identity; /* 8 bytes */
    ptp_clock_quality_t grandmaster_clock_quality; /* 4 bytes */
    uint8_t  grandmaster_priority2;          /* 1 byte */
    uint16_t steps_removed;                  /* 2 bytes */
    uint8_t  time_source;                    /* 1 byte */
} __attribute__((packed)) ptp_announce_msg_t;

/* 函数声明 */
void ptp_init_header(ptp_header_t *hdr, uint8_t msg_type, 
                     uint16_t seq_id, const ptp_port_identity_t *port_id);

void ptp_init_sync(ptp_sync_msg_t *msg, uint16_t seq_id,
                   const ptp_port_identity_t *port_id);

void ptp_init_follow_up(ptp_follow_up_msg_t *msg, uint16_t seq_id,
                        const ptp_port_identity_t *port_id,
                        const struct timespec *ts);

void ptp_init_delay_req(ptp_delay_req_msg_t *msg, uint16_t seq_id,
                        const ptp_port_identity_t *port_id);

void ptp_init_delay_resp(ptp_delay_resp_msg_t *msg, uint16_t seq_id,
                         const ptp_port_identity_t *port_id,
                         const ptp_timestamp_t *ts,
                         const ptp_port_identity_t *req_port_id);

void ptp_init_announce(ptp_announce_msg_t *msg, uint16_t seq_id,
                       const ptp_port_identity_t *port_id,
                       const struct timespec *ts);

#endif /* PTP_MESSAGE_H */

实现要点

  1. 消息头必须完整

    • 包含 correction_field (8字节)
    • 包含 reserved2 (4字节)
    • 总共34字节,符合IEEE 1588-2019标准
  2. Sync和Delay_Req消息体

    • 使用 origin_timestamp 字段
    • 不是reserved数组!
    • 这是IEEE 1588标准要求的
  3. Announce消息结构

    • 使用 ptp_clock_quality_t 结构体
    • 包含 steps_removed 字段
    • 完整的64字节

消息编码实现

ptp_message.c

c 复制代码
/**
 * @file ptp_message.c
 * @brief PTP消息编码实现 - IEEE 1588-2019
 */

#include <string.h>
#include <arpa/inet.h>
#include "ptp_message.h"

void ptp_init_header(ptp_header_t *hdr, uint8_t msg_type,
                     uint16_t seq_id, const ptp_port_identity_t *port_id)
{
    memset(hdr, 0, sizeof(*hdr));
    
    hdr->message_type = msg_type;
    hdr->version_ptp = PTP_VERSION;
    hdr->domain_number = PTP_DEFAULT_DOMAIN;
    hdr->flag_field = 0;
    hdr->correction_field = 0;
    
    memcpy(&hdr->source_port_identity, port_id, sizeof(ptp_port_identity_t));
    hdr->source_port_identity.port_number = htobe16(port_id->port_number);
    hdr->sequence_id = htobe16(seq_id);
    hdr->log_message_interval = 0x7F;
}

void ptp_init_sync(ptp_sync_msg_t *msg, uint16_t seq_id,
                   const ptp_port_identity_t *port_id)
{
    memset(msg, 0, sizeof(*msg));
    
    ptp_init_header(&msg->header, PTP_MSG_SYNC, seq_id, port_id);
    msg->header.message_length = htobe16(sizeof(*msg));
    msg->header.control_field = 0;
}

void ptp_init_follow_up(ptp_follow_up_msg_t *msg, uint16_t seq_id,
                        const ptp_port_identity_t *port_id,
                        const struct timespec *ts)
{
    memset(msg, 0, sizeof(*msg));
    
    ptp_init_header(&msg->header, PTP_MSG_FOLLOW_UP, seq_id, port_id);
    msg->header.message_length = htobe16(sizeof(*msg));
    msg->header.control_field = 2;
    
    timespec_to_ptp(ts, &msg->precise_origin_timestamp);
}

void ptp_init_delay_req(ptp_delay_req_msg_t *msg, uint16_t seq_id,
                        const ptp_port_identity_t *port_id)
{
    memset(msg, 0, sizeof(*msg));
    
    ptp_init_header(&msg->header, PTP_MSG_DELAY_REQ, seq_id, port_id);
    msg->header.message_length = htobe16(sizeof(*msg));
    msg->header.control_field = 1;
}

void ptp_init_delay_resp(ptp_delay_resp_msg_t *msg, uint16_t seq_id,
                         const ptp_port_identity_t *port_id,
                         const ptp_timestamp_t *ts,
                         const ptp_port_identity_t *req_port_id)
{
    memset(msg, 0, sizeof(*msg));
    
    ptp_init_header(&msg->header, PTP_MSG_DELAY_RESP, seq_id, port_id);
    msg->header.message_length = htobe16(sizeof(*msg));
    msg->header.control_field = 3;
    
    memcpy(&msg->receive_timestamp, ts, sizeof(ptp_timestamp_t));
    memcpy(&msg->requesting_port_identity, req_port_id, sizeof(ptp_port_identity_t));
    msg->requesting_port_identity.port_number = htobe16(req_port_id->port_number);
}

void ptp_init_announce(ptp_announce_msg_t *msg, uint16_t seq_id,
                       const ptp_port_identity_t *port_id,
                       const struct timespec *ts)
{
    memset(msg, 0, sizeof(*msg));
    
    ptp_init_header(&msg->header, PTP_MSG_ANNOUNCE, seq_id, port_id);
    msg->header.message_length = htobe16(sizeof(*msg));
    msg->header.control_field = 5;
    
    timespec_to_ptp(ts, &msg->origin_timestamp);
    msg->current_utc_offset = htobe16(37);
    msg->grandmaster_priority1 = PTP_DEFAULT_PRIORITY1;
    memcpy(msg->grandmaster_identity, port_id->clock_identity, 8);
    
    /* Clock Quality - IEEE 1588-2019 7.6.3.3 */
    msg->grandmaster_clock_quality.clock_class = 248;
    msg->grandmaster_clock_quality.clock_accuracy = 0xFE;
    msg->grandmaster_clock_quality.offset_scaled_log_variance = htobe16(0xFFFF);
    
    msg->grandmaster_priority2 = PTP_DEFAULT_PRIORITY2;
    msg->steps_removed = htobe16(0);
    msg->time_source = 0xA0; /* Internal Oscillator */
}

实现要点

  1. port_number字节序转换

    • 必须使用 htobe16 转换
    • ptp_init_header 中转换
    • ptp_init_delay_resp 中也要转换
  2. log_message_interval

    • 设置为 0x7F(初始值)
    • 表示默认消息间隔
  3. Announce消息的clock_quality

    • 完整设置三个字段
    • clock_class = 248(默认)
    • clock_accuracy = 0xFE(未知)
    • offset_scaled_log_variance = 0xFFFF

消息大小验证

c 复制代码
/* 编译时验证结构体大小 */
static_assert(sizeof(ptp_header_t) == 34, "Header size incorrect");
static_assert(sizeof(ptp_sync_msg_t) == 44, "Sync size incorrect");
static_assert(sizeof(ptp_follow_up_msg_t) == 44, "Follow_Up size incorrect");
static_assert(sizeof(ptp_delay_req_msg_t) == 44, "Delay_Req size incorrect");
static_assert(sizeof(ptp_delay_resp_msg_t) == 54, "Delay_Resp size incorrect");
static_assert(sizeof(ptp_announce_msg_t) == 64, "Announce size incorrect");

本章小结

关键概念

  1. ✅ 消息头必须完整(34字节,包含correction_field)
  2. ✅ Sync/Delay_Req使用origin_timestamp字段
  3. ✅ Announce使用ptp_clock_quality_t结构体
  4. ✅ port_number必须进行字节序转换
  5. ✅ 使用显式类型转换确保类型安全

完整源码

下一节:主时钟程序实现

📚 本文内容摘自本人的开源书《PTP技术书 - 从思想实验到协议实现》

全书从时间本质的思想实验出发,深度解析 IEEE 1588 协议、逐章分析 LinuxPTP 源码,并带你动手实现一个轻量级 PTP 程序(ptp-lite)。

🔗 在线阅读/下载:ptp-book

bash 复制代码
git clone https://github.com/Lularible/ptp-book.git

⭐ 如果对您有帮助,欢迎 Star 支持,也欢迎通过 GitHub Issues 交流讨论。

相关推荐
幽络源小助理1 小时前
IP定位系统源码二开版 新增分销功能 PHP地理位置查询系统
前端·开源·源码·php源码
Frank_refuel1 小时前
网络基础(一)
网络
发光小北10 小时前
Profinet 从站转 EtherNet/IP 从站网关如何应用?
网络·网络协议·tcp/ip
wangl_9210 小时前
Modbus RTU 与 Modbus TCP 深入指南-Wireshark抓包分析实战
网络协议·tcp/ip·wireshark·tcp·modbus·rtu
OurBMC社区10 小时前
玩转OurBMC第二十七期:BMC POST CODE解读
开源·ourbmc
高锰酸钾_11 小时前
计算机网络-链路层-介质访问控制
网络·计算机网络
哥只是传说中的小白11 小时前
GrsaiApi官方正版字字动画插件!支持nano banana pro和gpt-image-2模型
人工智能·gpt·ai作画·开源·aigc·api
源远流长jerry12 小时前
Linux 网络发送机制深度解析:从应用到网线
linux·服务器·网络·网络协议·tcp/ip
handler0113 小时前
UDP协议与网络通信知识点
c语言·网络·c++·笔记·网络协议·udp