TPSN 算法简介
TPSN (Timing-sync Protocol for Sensor Networks) 是一种经典的层次式时间同步协议,专为无线传感器网络设计,它的核心思想是成对同步,通过构建一个层次化的拓扑结构,将时间同步从根节点逐级传播到网络中的所有节点。

(图片来源网络,侵删)
主要特点
- 层次式结构: 网络中选举一个根节点(通常是 sink 或汇聚节点),其他节点根据与根节点的跳数分层,根节点在第0层,与根节点直接通信的节点在第1层,以此类推。
- 成对同步: 同步过程是基于发送方和接收方之间的一系列消息交换来完成的,精确测量消息的传输延迟。
- 双向消息交换: 为了消除发送方和接收方本身的处理延迟差异,TPSN 采用双向时间同步机制(类似 NTP 协议)。
- 可扩展性: 由于层次化的结构,TPSN 能够很好地扩展到大规模网络中。
同步原理:双向时间同步
TPSN 的核心是成对节点之间的双向时间同步,假设节点 A(发送方)和节点 B(接收方)需要进行同步,过程如下:
- T1: 节点 A 在本地时间
T1时刻发送一个SYNC消息给节点 B,该消息中包含T1。 - T2: 节点 B 在本地时间
T2时刻接收到SYNC消息。 - T3: 节点 B 立即在本地时间
T3时刻回复一个FOLLOW_UP消息给节点 A,该消息中包含T2和T3。 - T4: 节点 A 在本地时间
T4时刻接收到FOLLOW_UP消息。
我们有四个时间戳:
T1: 发送方发送时间T2: 接收方接收时间T3: 接收方回复时间T4: 发送方回复接收时间
我们可以建立以下方程来计算时钟偏差和延迟:
- 消息往返时间:
RTT = (T4 - T1) - (T3 - T2)(T4 - T1)是发送方测量的总时间。(T3 - T2)是接收方用于处理消息的时间。- RTT 代表了消息在链路上一个来回的总时间。
- 单程时间:
delay = RTT / 2假设上行和下行的链路延迟是对称的,单程延迟就是 RTT 的一半。
(图片来源网络,侵删) - 时钟偏差:
offset = (T2 - T1) - delay(T2 - T1)是接收方时钟相对于发送方时钟的原始偏移量。- 减去单程延迟
delay,就得到了接收方时钟相对于发送方时钟的真实偏差。
同步操作: 为了让接收方 B 的时钟与发送方 A 对齐,B 需要将自己的时间向前或向后调整 offset。offset 为正,说明 B 的时钟比 A 慢,需要加上 offset;如果为负,说明 B 的时钟比 A 快,需要减去 offset。
TPSN 算法流程
TPSN 的完整同步过程分为两个主要阶段:
层次构建
- 根节点选举: 网络中有一个预先确定的根节点(基站或汇聚节点),它位于第 0 层。
- 广播层级信息: 根节点向全网广播一个
LEVEL_DISCOVERY消息,声明自己是第 0 层。 - 节点分层:
- 网络中的其他节点监听
LEVEL_DISCOVERY消息。 - 一个节点会记录下所有它能听到的
LEVEL_DISCOVERY消息,并选择其中层级最小的发送方作为其父节点。 - 该节点的层级 = 其父节点的层级 + 1。
- 该节点也广播一个
LEVEL_DISCOVERY消息,声明自己的新层级。
- 网络中的其他节点监听
- 迭代完成: 这个过程持续进行,直到网络中所有节点都确定了各自的层级,形成一个以根节点为根的树状拓扑结构。
同步阶段
- 从根节点开始: 同步从第 0 层的根节点开始。
- 逐层向下同步:
- 第 0 层 (根节点): 不需要同步。
- 第 1 层节点: 每个第 1 层节点都与根节点执行双向时间同步过程(如上所述),同步完成后,第 1 层节点的时钟就与根节点同步了。
- 第 2 层节点: 每个第 2 层节点只与其父节点(一个第 1 层节点)执行双向时间同步。
- ... 以此类推: 第
k层的节点只与其父节点(第k-1层的节点)进行同步。
- 完成: 当所有层的节点都完成同步后,整个网络就达到了时间同步。
C 语言实现
下面是一个简化版的 TPSN 算法 C 语言实现,这个模拟程序将创建一个网络拓扑,并执行 TPSN 的两个阶段。
1 代码结构
node.h: 定义节点结构体和消息类型。tpsn.c: 主程序,包含网络初始化、层次构建、同步执行等核心逻辑。utils.h/c: 辅助函数,如模拟发送/接收消息、打印网络状态等。
2 代码实现
node.h - 节点和消息定义
#ifndef NODE_H
#define NODE_H
#include <stdbool.h>
#include <stdint.h>
// 模拟的时间戳类型,可以是高精度计时器的值
typedef uint64_t timestamp_t;
// 节点状态
typedef enum {
LEVEL_NOT_SET,
LEVEL_SET,
SYNCHRONIZED
} NodeState;
// 节点结构体
typedef struct {
int id; // 节点ID
int level; // 节点层级
int parent_id; // 父节点ID
NodeState state; // 节点状态
double clock_offset; // 相对于根节点的时钟偏移量(用于模拟)
timestamp_t local_time; // 本地模拟时间
} Node;
// 消息类型
typedef enum {
LEVEL_DISCOVERY, // 层级发现消息
SYNC, // 同步请求消息
FOLLOW_UP // 同步回复消息
} MessageType;
// 消息结构体
typedef struct {
MessageType type;
int sender_id;
int receiver_id; // 仅用于模拟,实际无线通信是广播的
timestamp_t timestamp1; // T1 (for SYNC) or T2 (for FOLLOW_UP)
timestamp_t timestamp2; // T3 (for FOLLOW_UP)
} Message;
#endif // NODE_H
utils.h - 辅助函数声明
#ifndef UTILS_H #define UTILS_H #include "node.h" #include <stdio.h> // 模拟发送消息 void send_message(Node* nodes, int num_nodes, Message msg); // 模拟接收消息 void receive_message(Node* nodes, int num_nodes, Node* receiver, Message* msg); // 打印网络当前状态(层级和状态) void print_network_status(Node* nodes, int num_nodes); // 模拟时间流逝 void simulate_time_passage(Node* nodes, int num_nodes, uint64_t time_elapsed); #endif // UTILS_H
utils.c - 辅助函数实现
#include "utils.h"
#include <string.h>
#include <stdlib.h>
#include <time.h>
// 模拟网络延迟,单位:微秒
#define NETWORK_DELAY_US 1000
// 模拟处理延迟,单位:微秒
#define PROCESSING_DELAY_US 100
// 获取当前模拟时间(这里用系统时间模拟,实际中可能是硬件计数器)
timestamp_t get_current_time() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (timestamp_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
void send_message(Node* nodes, int num_nodes, Message msg) {
// 在真实网络中,这是广播,这里简化为直接调用接收方的函数。
// msg.receiver_id 在调用时由发送方逻辑确定。
printf("Node %d sends %s message to Node %d\n", msg.sender_id,
msg.type == LEVEL_DISCOVERY ? "LEVEL_DISCOVERY" :
msg.type == SYNC ? "SYNC" : "FOLLOW_UP", msg.receiver_id);
// 模拟网络传输延迟
simulate_time_passage(nodes, num_nodes, NETWORK_DELAY_US);
}
void receive_message(Node* nodes, int num_nodes, Node* receiver, Message* msg) {
// 模拟处理延迟
simulate_time_passage(nodes, num_nodes, PROCESSING_DELAY_US);
receiver->local_time = get_current_time(); // 更新接收方的本地时间
printf("Node %d received %s message from Node %d at its local time: %llu\n",
receiver->id, msg->type == LEVEL_DISCOVERY ? "LEVEL_DISCOVERY" :
msg->type == SYNC ? "SYNC" : "FOLLOW_UP", msg->sender_id, (unsigned long long)receiver->local_time);
}
void print_network_status(Node* nodes, int num_nodes) {
printf("\n--- Network Status ---\n");
printf("ID | Level | Parent | State | Clock Offset\n");
printf("------------------------------------------------\n");
for (int i = 0; i < num_nodes; i++) {
const char* state_str = nodes[i].state == LEVEL_NOT_SET ? "NOT_SET" :
nodes[i].state == LEVEL_SET ? "SET" : "SYNC";
printf("%2d | %5d | %6d | %10s | %12.3f\n",
nodes[i].id, nodes[i].level, nodes[i].parent_id, state_str, nodes[i].clock_offset);
}
printf("----------------------\n\n");
}
void simulate_time_passage(Node* nodes, int num_nodes, uint64_t time_elapsed) {
// 在真实系统中,这是物理时间的流逝。
// 在我们的模拟中,我们为每个节点的“本地时钟”增加一个随机偏移来模拟时钟漂移。
// 为了简化,这里我们只增加一个固定值,代表时间的流逝。
// 更复杂的模拟可以为每个节点添加不同的漂移率。
for (int i = 0; i < num_nodes; i++) {
nodes[i].local_time += time_elapsed;
}
// 让主线程暂停,模拟真实时间流逝
// usleep(time_elapsed);
}
tpsn.c - 主程序和 TPSN 算法逻辑
#include "node.h"
#include "utils.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#define NUM_NODES 6
// --- 阶段一:层次构建 ---
void build_levels(Node* nodes, int num_nodes) {
printf("--- Starting Level Building Phase ---\n");
// 根节点是ID为0的节点
nodes[0].level = 0;
nodes[0].state = LEVEL_SET;
nodes[0].parent_id = -1; // 根节点没有父节点
bool levels_updated;
do {
levels_updated = false;
// 遍历所有已确定层级的节点
for (int i = 0; i < num_nodes; i++) {
if (nodes[i].state == LEVEL_SET) {
// 模拟该节点广播 LEVEL_DISCOVERY 消息
Message msg;
msg.type = LEVEL_DISCOVERY;
msg.sender_id = nodes[i].id;
msg.timestamp1 = nodes[i].local_time; // 发送时间
// 遍历所有未确定层级的节点
for (int j = 0; j < num_nodes; j++) {
if (nodes[j].state == LEVEL_NOT_SET) {
msg.receiver_id = j;
send_message(nodes, num_nodes, msg);
// 模拟接收方处理消息
Node* receiver = &nodes[j];
receive_message(nodes, num_nodes, receiver, &msg);
// 接收方逻辑:如果发现层级更小的父节点,则更新
if (receiver->parent_id == -1 || nodes[i].level < nodes[receiver->parent_id].level) {
receiver->parent_id = nodes[i].id;
// 注意:这里不立即设置层级,等待一轮广播结束
}
}
}
}
}
// 一轮广播结束后,更新所有找到父节点的节点的层级
for (int i = 0; i < num_nodes; i++) {
if (nodes[i].state == LEVEL_NOT_SET && nodes[i].parent_id != -1) {
nodes[i].level = nodes[nodes[i].parent_id].level + 1;
nodes[i].state = LEVEL_SET;
levels_updated = true;
printf("Node %d set level to %d, parent is Node %d\n",
nodes[i].id, nodes[i].level, nodes[i].parent_id);
}
}
} while (levels_updated); // 直到没有节点能更新层级为止
printf("--- Level Building Phase Finished ---\n");
print_network_status(nodes, num_nodes);
}
// --- 阶段二:同步 ---
void perform_synchronization(Node* nodes, int num_nodes) {
printf("\n--- Starting Synchronization Phase ---\n");
// 按层级从低到高排序,确保先同步低层级节点
// 这里简化处理,假设节点ID与层级顺序相关,实际中应排序
// for (int current_level = 1; current_level < MAX_LEVEL; current_level++) {
for (int i = 0; i < num_nodes; i++) {
if (nodes[i].level == 0) continue; // 跳过根节点
Node* child = &nodes[i];
Node* parent = &nodes[child->parent_id];
printf("\nSynchronizing Node %d (level %d) with its parent Node %d (level %d)...\n",
child->id, child->level, parent->id, parent->level);
// --- 双向同步过程 ---
// 1. T1: Parent sends SYNC to child
Message sync_msg;
sync_msg.type = SYNC;
sync_msg.sender_id = parent->id;
sync_msg.receiver_id = child->id;
sync_msg.timestamp1 = parent->local_time; // T1
send_message(nodes, num_nodes, sync_msg);
receive_message(nodes, num_nodes, child, &sync_msg);
timestamp_t T1 = sync_msg.timestamp1;
timestamp_t T2 = child->local_time; // T2 is child's local time upon reception
// 2. T3: Child sends FOLLOW_UP to parent
Message follow_up_msg;
follow_up_msg.type = FOLLOW_UP;
follow_up_msg.sender_id = child->id;
follow_up_msg.receiver_id = parent->id;
follow_up_msg.timestamp1 = T2; // T2
follow_up_msg.timestamp2 = child->local_time; // T3
send_message(nodes, num_nodes, follow_up_msg);
receive_message(nodes, num_nodes, parent, &follow_up_msg);
timestamp_t T3 = follow_up_msg.timestamp2;
timestamp_t T4 = parent->local_time; // T4 is parent's local time upon reception
// 3. 计算偏差和延迟
int64_t rtt = (T4 - T1) - (T3 - T2);
double delay = (double)rtt / 2.0;
double offset = (T2 - T1) - delay;
printf(" Timestamps: T1=%llu, T2=%llu, T3=%llu, T4=%llu\n",
(unsigned long long)T1, (unsigned long long)T2, (unsigned long long)T3, (unsigned long long)T4);
printf(" Calculated RTT: %lld, Delay: %.3f, Offset: %.3f\n",
(long long)rtt, delay, offset);
// 4. 子节点调整时钟
child->clock_offset += offset; // 累积偏移
child->state = SYNCHRONIZED;
printf(" Node %d adjusted its clock by %.3f. New offset: %.3f\n",
child->id, offset, child->clock_offset);
}
printf("--- Synchronization Phase Finished ---\n");
print_network_status(nodes, num_nodes);
}
int main() {
Node nodes[NUM_NODES];
// 初始化所有节点
for (int i = 0; i < NUM_NODES; i++) {
nodes[i].id = i;
nodes[i].level = -1;
nodes[i].parent_id = -1;
nodes[i].state = LEVEL_NOT_SET;
nodes[i].clock_offset = 0.0;
nodes[i].local_time = get_current_time();
// 模拟初始时钟不同步
nodes[i].local_time += i * 500; // 每个节点初始时间有微小差异
}
printf("Initial Network Status:\n");
print_network_status(nodes, NUM_NODES);
// 执行TPSN算法
build_levels(nodes, NUM_NODES);
perform_synchronization(nodes, NUM_NODES);
return 0;
}
3 如何编译和运行
-
将以上四个代码块分别保存为
node.h,utils.h,utils.c, 和tpsn.c。
(图片来源网络,侵删) -
使用 GCC 进行编译(需要链接
rt库以支持clock_gettime):gcc tpsn.c utils.c -o tpsn_simulation -lrt
-
运行程序:
./tpsn_simulation
4 预期输出
运行程序后,你将看到详细的日志输出,包括:
- 初始网络状态: 所有节点层级未设置。
- 层次构建阶段: 根节点(0)广播,其他节点逐级确定自己的层级和父节点。
- 同步阶段: 从第1层节点开始,每个节点与其父节点执行双向同步,打印出时间戳、计算出的延迟和偏差,并最终更新自己的时钟偏移量。
- 最终网络状态: 所有节点都处于
SYNCHRONIZED状态,并显示其相对于根节点的累积时钟偏移。
这个 C 语言实现清晰地展示了 TPSN 算法的两个核心阶段,并模拟了其关键的消息交换和时间戳计算过程,虽然它是一个简化版本(没有考虑消息丢失、更复杂的时钟漂移模型),但它完美地诠释了 TPSN 的工作原理。
