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 交流讨论。

相关推荐
Freak嵌入式4 小时前
一文搞懂树莓派 Pico 任务调度器:基于定时器的 MicroPython 多任务实现
嵌入式
草莓熊Lotso5 小时前
【Redis 初阶】Set 类型深度解析:去重集合的运算能力与实战场景
linux·网络·数据库·windows·redis·tcp/ip·缓存
苏灿烤鱼5 小时前
当 AI Agent 遇见真实科学环境:深度拆解 Scientific Agent Skills,把"聊天机器人"变成"AI 科学家"
python·开源·agent
XR1234567885 小时前
汽车制造园区网络怎么建?柔性产线与 AGV 的选型逻辑
网络·汽车·制造
stormzhangV11 小时前
微信内部的生产级模型,居然开源了
开源·ai编程
IT大白鼠15 小时前
MSF二次开发与自定义模块编写
网络·安全·web安全·msf
Julien200415 小时前
调查和解决 SELinux 问题
linux·运维·服务器·网络·学习方法
冬奇Lab16 小时前
Code Agent 解剖(15):Harness 设计之五——可观测性
人工智能·开源
冬奇Lab16 小时前
开源项目第203期:Cumora — AI Agent 与人类同队的跨平台团队协作工具
人工智能·开源·资讯
kekekzt17 小时前
TCP协议的粘包问题介绍,IP分片,MTU,MSS,滑动窗口的概念及之间的关系
网络·网络协议·tcp/ip