3.12 单播协商实现:PTP的“专线服务“
为什么需要单播
默认PTP使用组播,但有些场景需要单播:
组播的局限性:
1. 跨网段问题
- 组播可能被路由器阻止
- TTL限制传播范围
- 需要组播路由支持
2. 网络负载
- 所有设备都收到所有报文
- 浪费带宽
- 不必要的处理
3. 精确控制
- 无法指定特定主时钟
- 无法控制服务质量
- 无法按需分配资源
单播的优势:
1. 跨网段
- 穿越路由器
- 无组播限制
- 更广的覆盖范围
2. 按需服务
- 只发送给请求者
- 节省网络带宽
- 精确控制目标
3. 服务质量
- 可协商发送速率
- 可协商持续时间
- 灵活的参数配置
适用场景:
- 跨路由器的PTP
- 电信运营商网络
- 数据中心互联
单播协商协议
信令消息
单播协商使用Signaling消息:
Signaling Message结构:
┌─────────┬─────────┬──────────┬──────────┐
│Header │Target │TLV │TLV │
│34 bytes │Port ID │(Request) │(Grant) │
│ │10 bytes │Variable │Variable │
└─────────┴─────────┴──────────┴──────────┘
包含的TLV:
- REQUEST_UNICAST_TRANSMISSION
- GRANT_UNICAST_TRANSMISSION
- CANCEL_UNICAST_TRANSMISSION
- ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION
协商流程
单播协商三步握手:
步骤1:请求(从时钟→ 主时钟)
Signaling消息包含:
- REQUEST_UNICAST_TRANSMISSION TLV
- messageType:请求的消息类型(Sync/Announce/Delay_Resp)
- logInterMessagePeriod:发送间隔
- durationField:请求的持续时间(秒)
步骤2:授权(主时钟 → 从时钟)
Signaling消息包含:
- GRANT_UNICAST_TRANSMISSION TLV
- messageType:同意的消息类型
- logInterMessagePeriod:实际发送间隔
- durationField:授权的持续时间
- flags:授权标志
步骤3:开始传输
主时钟按协商参数发送单播消息
从时钟接收并处理
步骤4:取消(可选)
任何一方可以发送CANCEL_UNICAST_TRANSMISSION
对方响应ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION
TLV结构详解
REQUEST_UNICAST_TRANSMISSION
/* tlv.h, 第283-289行 */
struct request_unicast_xmit_tlv {
Enumeration16type; /* 0x0004 */
UInteger16length;
uint8_tmessage_type; /* 请求的消息类型 */
Integer8 logInterMessagePeriod; /* 发送间隔(log2秒) */
UInteger32 durationField; /* 持续时间(秒) */
};
消息类型编码:
message_type字段:
bit 7-4:消息类型
bit 3-0:保留
编码:
0x10 = ANNOUNCE
0x20 = SYNC
0x40 = DELAY_RESP
0x50 = PDELAY_RESP
示例:
请求Sync消息:message_type = 0x20
请求Announce消息:message_type = 0x10
logInterMessagePeriod:
发送间隔 = 2^logInterMessagePeriod 秒
示例:
0:每1秒发送一次
-3:每0.125秒发送一次(125ms)
3:每8秒发送一次
GRANT_UNICAST_TRANSMISSION
/* tlv.h, 第169-177行 */
struct grant_unicast_xmit_tlv {
Enumeration16 type; /* 0x0005 */
UInteger16 length;
uint8_t message_type; /* 消息类型 */
Integer8 logInterMessagePeriod; /* 发送间隔 */
UInteger32 durationField; /* 授权时长 */
uint8_t reserved;
uint8_t flags; /* 授权标志 */
};
flags字段:
/* tlv.h, 第150行 */
#define GRANT_UNICAST_RENEWAL_INVITED (1 << 0)
flags含义:
bit 0:RENEWAL_INVITED
- 1:主时钟邀请从时钟续约
- 0:不邀请续约
续约流程:
1. 授权即将到期
2. 主时钟设置RENEWAL_INVITED = 1
3. 从时钟收到后可以提前请求续约
4. 避免服务中断
其他bits保留。
CANCEL_UNICAST_TRANSMISSION
/* tlv.h, 第162-167行 */
struct cancel_unicast_xmit_tlv {
Enumeration16 type; /* 0x0006 */
UInteger16 length;
uint8_t message_type_flags; /* 消息类型和标志 */
uint8_t reserved;
};
取消流程:
取消场景:
1. 从时钟不再需要服务
- 断开连接
- 切换到其他主时钟
- 停止同步
2. 主时钟无法继续服务
- 资源不足
- 设备关闭
- 配置变更
取消消息:
CANCEL_UNICAST_TRANSMISSION TLV
- message_type_flags:要取消的消息类型
确认消息:
ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION TLV
- 确认收到取消请求
- 停止发送/接收
LinuxPTP实现
unicast_client结构
/* port.h中定义 */
struct unicast_client {
LIST_ENTRY(unicast_client) list;
struct PortIdentity portIdentity; /* 目标端口ID */
struct PortAddress address; /* 目标地址 */
uint8_t message_type; /* 消息类型 */
Integer8 logInterMessagePeriod; /* 发送间隔 */
UInteger32 durationField; /* 剩余时长 */
struct timespec grant_time; /* 授权时间 */
int state; /* 状态 */
};
发送请求
/* port.c中的单播请求(简化) */
static int port_request_unicast(struct port *p, struct unicast_client *client)
{
struct ptp_message *msg;
struct request_unicast_xmit_tlv *req;
/* 构造Signaling消息 */
msg = msg_allocate();
msg->header.messageType = SIGNALING;
/* 设置目标 */
msg->signaling.targetPortIdentity = client->portIdentity;
/* 构造REQUEST TLV */
req = (struct request_unicast_xmit_tlv *) msg->management.suffix;
req->type = TLV_REQUEST_UNICAST_TRANSMISSION;
req->length = sizeof(*req) - sizeof(struct TLV);
req->message_type = client->message_type;
req->logInterMessagePeriod = client->logInterMessagePeriod;
req->durationField = client->durationField;
/* 字节序转换 */
tlv_pre_send((struct TLV *)req, NULL);
/* 发送 */
return port_tx(p, msg, p->trp);
}
处理授权
/* port.c中的授权处理(简化) */
static void port_handle_grant(struct port *p, struct grant_unicast_xmit_tlv *grant)
{
struct unicast_client *client;
/* 字节序转换 */
grant->durationField = ntohl(grant->durationField);
/* 查找对应的请求 */
client = find_unicast_client(p, grant->message_type);
if (!client)
return;
/* 更新参数 */
client->logInterMessagePeriod = grant->logInterMessagePeriod;
client->durationField = grant->durationField;
client->state = UNICAST_GRANTED;
/* 记录授权时间 */
clock_gettime(CLOCK_MONOTONIC, &client->grant_time);
pr_info("unicast grant received: type %u, interval %d, duration %u",
grant->message_type >> 4,
client->logInterMessagePeriod,
client->durationField);
}
发送单播消息
/* port.c中的单播发送(简化) */
static int port_tx_unicast(struct port *p, struct ptp_message *msg,
struct unicast_client *client)
{
/* 设置目标地址 */
msg->address = client->address;
/* 使用transport_sendto发送到特定地址 */
return transport_sendto(p->trp, &p->fda, TRANS_EVENT, msg);
}
配置示例
从时钟配置
# /etc/linuxptp/ptp4l.conf
[global]
# 启用单播协商
unicast_nego_enabledefault1
# 单播主时钟表
unicast_master_tabletable1
[table1]
# 表索引
table_id 1
# 主时钟列表
logAnnounceInterval1
logSyncInterval -3
logDelayReqInterval-3
# 主时钟地址
# 格式:addresspriority
master_address 192.168.1.100 128
master_address 192.168.1.101 127
[eth0]
# 使用单播主时钟表
unicast_master_tabletable1
主时钟配置
# /etc/linuxptp/ptp4l.conf
[global]
# 启用单播服务
unicast_nego_enabledefault1
# 允许单播请求
inhibit_multicast_service 0
[eth0]
# 主时钟优先级
priority1128
priority2128
启动命令
# 从时钟启动
ptp4l -i eth0 -f /etc/linuxptp/ptp4l.conf -m
# 主时钟启动
ptp4l -i eth0 -m -S
# 查看单播状态
pmc -u "GET UNICAST_MASTER_TABLE_NP"
单播 vs 组播对比
| 特性 | 组播 | 单播 |
|---|---|---|
| 地址 | 224.0.1.129 | 特定IP |
| 覆盖范围 | 子网内 | 可跨网段 |
| 网络负载 | 所有设备接收 | 仅目标接收 |
| 配置复杂度 | 简单 | 较复杂 |
| 动态性 | 自动发现 | 需要协商 |
| 延迟测量 | E2E或P2P | E2E |
| 适用场景 | 局域网 | 广域网、跨网段 |
小结:单播协商的关键要点
核心概念:
- Signaling消息
- 三步握手:请求→ 授权 → 传输
TLV类型:
- REQUEST_UNICAST_TRANSMISSION
- GRANT_UNICAST_TRANSMISSION
- CANCEL_UNICAST_TRANSMISSION
- ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION
协商参数:
- 消息类型
- 发送间隔
- 持续时间
适用场景:
- 跨网段PTP
- 按需服务
- 精确控制
下集预告
单播解决了“如何建立专线服务“,但网络中的故障如何诊断?
下一节,我们将分析故障处理与诊断——看看LinuxPTP如何应对各种故障场景。
【悬念留给3.13】
PTP网络中可能遇到各种故障。
如何检测链路故障?
如何处理主时钟丢失?
如何诊断同步问题?
下一节,最后一节源码分析。