4.2 消息结构与编码:PTP报文的"DNA"
源码版本说明
本章源码片段基于 ptp-lite v1.0.0 (2026-04-10)。
⚠️ 重要提示:
- 完整、最新的源码请查看
ptp_lite/目录- 本章代码片段仅用于理解原理,可能不是最新版本
- 如发现差异,以源码为准
- 源码变更记录请查看 CHANGELOG.md
主要源码文件:
- ptp_common.h - 公共定义(83行)
- ptp_message.h - 消息结构定义(96行)
- ptp_message.c - 消息编码实现(99行)
消息格式总览
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 */
实现要点:
-
时间戳格式:
- 48位秒 + 32位纳秒
- 符合IEEE 1588-2019标准
- 使用
__attribute__((packed))避免内存对齐
-
字节序转换:
- PTP使用大端序(网络字节序)
- 必须使用
htobe16、htobe32等函数 - 接收时使用
be16toh、be32toh等
-
类型安全:
- 使用显式类型转换,避免编译警告
- 特别是
uint64_t到uint16_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 */
实现要点:
-
消息头必须完整:
- 包含
correction_field(8字节) - 包含
reserved2(4字节) - 总共34字节,符合IEEE 1588-2019标准
- 包含
-
Sync和Delay_Req消息体:
- 使用
origin_timestamp字段 - 不是reserved数组!
- 这是IEEE 1588标准要求的
- 使用
-
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 */
}
实现要点:
-
port_number字节序转换:
- 必须使用
htobe16转换 - 在
ptp_init_header中转换 - 在
ptp_init_delay_resp中也要转换
- 必须使用
-
log_message_interval:
- 设置为
0x7F(初始值) - 表示默认消息间隔
- 设置为
-
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");
本章小结
关键概念:
- ✅ 消息头必须完整(34字节,包含correction_field)
- ✅ Sync/Delay_Req使用origin_timestamp字段
- ✅ Announce使用ptp_clock_quality_t结构体
- ✅ port_number必须进行字节序转换
- ✅ 使用显式类型转换确保类型安全
完整源码:
- ptp_common.h - 公共定义(83行)
- ptp_message.h - 消息结构(96行)
- ptp_message.c - 编码实现(99行)
下一节:主时钟程序实现
📚 本文内容摘自本人的开源书《PTP技术书 - 从思想实验到协议实现》
全书从时间本质的思想实验出发,深度解析 IEEE 1588 协议、逐章分析 LinuxPTP 源码,并带你动手实现一个轻量级 PTP 程序(ptp-lite)。
🔗 在线阅读/下载:ptp-book
bash
git clone https://github.com/Lularible/ptp-book.git
⭐ 如果对您有帮助,欢迎 Star 支持,也欢迎通过 GitHub Issues 交流讨论。