4.3 主时钟程序实现:时间的“发布者“
主时钟的职责
主时钟负责发布时间信息:
主时钟任务:
1. 发送Announce消息
- 告知从时钟自己的存在
- 携带时钟质量信息
- 用于BMCA选举
2. 发送Sync消息
- 事件消息,需要时间戳
- 携带序列号
- 定期发送
3. 发送Follow_Up消息
- 携带Sync的发送时间戳
- 与Sync配对
- 普通消息
4. 响应Delay_Req消息
- 接收从时钟的Delay_Req
- 记录接收时间戳
- 发送Delay_Resp
工作流程:
Announce → Sync → Follow_Up → (等待Delay_Req) → Delay_Resp
↑___________________________________|
完整主时钟代码
/**
* @file ptp_master.c
* @brief PTP主时钟程序
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include "ptp_message.h"
#include "ptp_servo.h"
static ptp_port_identity_t master_port_id;
static uint16_t sync_seq = 0;
static uint16_t announce_seq = 0;
static uint16_t delay_resp_seq = 0;
static int create_socket(const char *iface)
{
int fd;
struct sockaddr_in addr;
struct ip_mreqn mreq;
int opt = 1;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return -1;
}
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(PTP_EVENT_PORT);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(fd);
return -1;
}
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface)) < 0) {
perror("SO_BINDTODEVICE");
}
memset(&mreq, 0, sizeof(mreq));
inet_pton(AF_INET, PTP_PRIMARY_MCAST, &mreq.imr_multiaddr);
mreq.imr_ifindex = if_nametoindex(iface);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
perror("IP_ADD_MEMBERSHIP");
}
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof(mreq));
return fd;
}
static int send_multicast(int fd, const void *data, size_t len, int port)
{
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET, PTP_PRIMARY_MCAST, &addr.sin_addr);
addr.sin_port = htons(port);
return sendto(fd, data, len, 0, (struct sockaddr *)&addr, sizeof(addr));
}
static int send_unicast(int fd, const void *data, size_t len, struct sockaddr_in *client_addr)
{
return sendto(fd, data, len, 0, (struct sockaddr *)client_addr, sizeof(*client_addr));
}
static void init_port_id(void)
{
memset(&master_port_id, 0, sizeof(master_port_id));
master_port_id.clock_identity[0] = 0x00;
master_port_id.clock_identity[1] = 0x11;
master_port_id.clock_identity[2] = 0x22;
master_port_id.clock_identity[3] = 0x33;
master_port_id.clock_identity[4] = 0x44;
master_port_id.clock_identity[5] = 0x55;
master_port_id.clock_identity[6] = 0x66;
master_port_id.clock_identity[7] = 0x77;
master_port_id.port_number = htobe16(1);
}
static void send_announce(int fd)
{
ptp_announce_msg_t msg;
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ptp_init_announce(&msg, announce_seq++, &master_port_id, &ts);
send_multicast(fd, &msg, sizeof(msg), PTP_GENERAL_PORT);
printf("Sent Announce seq=%u\n", be16toh(msg.header.sequence_id));
}
static void send_sync(int fd)
{
ptp_sync_msg_t sync_msg;
ptp_follow_up_msg_t follow_up_msg;
struct timespec ts;
ssize_t ret;
ptp_init_sync(&sync_msg, sync_seq, &master_port_id);
ret = send_multicast(fd, &sync_msg, sizeof(sync_msg), PTP_EVENT_PORT);
if (ret > 0) {
clock_gettime(CLOCK_REALTIME, &ts);
ptp_init_follow_up(&follow_up_msg, sync_seq, &master_port_id, &ts);
send_multicast(fd, &follow_up_msg, sizeof(follow_up_msg), PTP_GENERAL_PORT);
printf("Sent Sync+Follow_Up seq=%u time=%ld.%09ld\n", sync_seq, ts.tv_sec, ts.tv_nsec);
}
sync_seq++;
}
static void handle_delay_req(int fd, uint8_t *data, struct sockaddr_in *client_addr)
{
ptp_delay_req_msg_t *req = (ptp_delay_req_msg_t *)data;
ptp_delay_resp_msg_t resp;
struct timespec ts;
ptp_timestamp_t recv_ts;
clock_gettime(CLOCK_REALTIME, &ts);
timespec_to_ptp(&ts, &recv_ts);
ptp_init_delay_resp(&resp, delay_resp_seq++, &master_port_id, &recv_ts, &req->header.source_port_identity);
send_unicast(fd, &resp, sizeof(resp), client_addr);
printf("Sent Delay_Resp seq=%u time=%ld.%09ld\n", be16toh(resp.header.sequence_id), ts.tv_sec, ts.tv_nsec);
}
int main(int argc, char *argv[])
{
int fd;
uint8_t recv_buf[1024];
struct timespec next_announce, next_sync, now;
struct sockaddr_in client_addr;
socklen_t addr_len;
ssize_t ret;
if (argc < 2) {
fprintf(stderr, "Usage: %s <interface>\n", argv[0]);
return 1;
}
printf("Starting PTP Master on interface %s\n", argv[1]);
init_port_id();
fd = create_socket(argv[1]);
if (fd < 0) {
fprintf(stderr, "Failed to create socket\n");
return 1;
}
printf("Master Clock ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
master_port_id.clock_identity[0], master_port_id.clock_identity[1],
master_port_id.clock_identity[2], master_port_id.clock_identity[3],
master_port_id.clock_identity[4], master_port_id.clock_identity[5],
master_port_id.clock_identity[6], master_port_id.clock_identity[7]);
clock_gettime(CLOCK_MONOTONIC, &next_announce);
clock_gettime(CLOCK_MONOTONIC, &next_sync);
next_announce.tv_sec += 2;
next_sync.tv_sec += 1;
while (1) {
fd_set readfds;
struct timeval timeout;
clock_gettime(CLOCK_MONOTONIC, &now);
if (now.tv_sec >= next_announce.tv_sec) {
send_announce(fd);
next_announce.tv_sec = now.tv_sec + 2;
}
if (now.tv_sec >= next_sync.tv_sec) {
send_sync(fd);
next_sync.tv_sec = now.tv_sec + 1;
}
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
if (ret > 0 && FD_ISSET(fd, &readfds)) {
addr_len = sizeof(client_addr);
ret = recvfrom(fd, recv_buf, sizeof(recv_buf), 0,
(struct sockaddr *)&client_addr, &addr_len);
if (ret > (ssize_t)sizeof(ptp_header_t)) {
ptp_header_t *hdr = (ptp_header_t *)recv_buf;
if (hdr->message_type == PTP_MSG_DELAY_REQ) {
handle_delay_req(fd, recv_buf, &client_addr);
}
}
}
}
close(fd);
return 0;
}
关键点说明
组播发送
/* 设置组播目标 */
struct sockaddr_in addr;
inet_pton(AF_INET, "224.0.1.129", &addr.sin_addr);
addr.sin_port = htons(319);
/* 发送 */
sendto(fd, data, len, 0, (struct sockaddr *)&addr, sizeof(addr));
时间戳获取
/* 软件时间戳:使用系统时钟 */
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
/* 注意:精度受限(微秒级) */
消息配对
/* Sync和Follow_Up使用相同的序列号 */
ptp_init_sync(&sync_msg, sync_seq, ...);
send_sync(...);
ptp_init_follow_up(&follow_up_msg, sync_seq, ...);
send_follow_up(...);
sync_seq++; /* 两个消息发送后才递增 */
运行主时钟
# 编译
make
# 运行(需要root权限)
sudo ./ptp_master eth0
# 输出示例:
Starting PTP Master on interface eth0
Master Clock ID: 00:11:22:33:44:55:66:77
Sent Announce seq=0
Sent Sync+Follow_Up seq=0 time=1234567890.123456789
Sent Delay_Resp seq=0 time=1234567890.234567890
...
小结
我们实现了完整的PTP主时钟:
- 发送Announce消息
- 发送Sync+Follow_Up消息
- 响应Delay_Req消息
下一节,我们将实现从时钟程序——这是同步的核心。
【悬念留给4.4】
主时钟已经发布时间。
从时钟如何接收并处理?
如何计算时间偏差?
如何调整系统时钟?
下一节,从时钟实现。