0. 什么是 TIPC

TIPC(Transparent Inter-Process Communication,透明进程间通信)是 Linux 内核提供的一种专用于集群内部通信的高性能网络协议。

它的核心思想是:让分布式系统中的进程通信像本地 IPC 一样简单,但又能跨多个节点工作。

比如,传统方式要用 IP + 端口定位服务,还得自己做服务发现和健康检查;而 TIPC 允许你直接用一个自定义的‘服务地址’(比如类型=日志服务,实例=101)来发送消息,底层自动帮你找到这个服务当前运行在哪个节点上,并保证消息可靠、有序地送达。

它还内置了服务状态跟踪功能——你可以订阅某个服务是否上线或下线,甚至监控整个集群的节点加入/离开事件。

所以 TIPC 特别适合对低延迟、高可靠、强一致性有要求的场景,比如电信级系统、金融交易中间件或高可用微服务架构。

1. 核心目标

TIPC 旨在提供一种比传统方案(如 Unix 域套接字或基于 IP 的 TCP/UDP)更方便、更高效的集群内通信方式。它解决了在集群环境中使用 IP 地址和 DNS 查找的复杂性,并提供了内置的节点和服务状态监控功能。

2. 主要特性

集群范围的 IPC 服务

  • 它让你可以像使用本地 Unix 域套接字一样方便地进行通信,但通信范围可以跨越整个集群中的不同节点。

  • 开发者可以自定义服务地址,而无需关心底层的 IP 地址。

服务寻址 (Service Addressing)

  • 程序员可以为自己创建的服务绑定一个自定义的“服务地址”(由类型 type 和实例 instance 组成)。

  • 客户端只需使用这个服务地址即可发送消息,无需知道服务运行在哪个具体的节点上。

服务跟踪 (Service Tracking)

  • 这是 TIPC 的一个强大功能。客户端可以订阅某个服务地址的“绑定”(服务上线)和“解绑”(服务下线)事件。

  • 此机制还可用于集群拓扑跟踪(监控哪些节点在线/离线)和集群连接性跟踪(监控节点间的链路状态)。

多种传输模式

  • 数据报 (Datagram):无连接的消息发送。

  • 面向连接 (Connection-oriented):建立可靠的点对点连接。

  • 通信组 (Communication Group):实现一个无代理(brokerless)的消息总线,支持高性能、可扩展的多播 (Multicast)。

高可靠性和性能

  • 保证消息顺序、无丢失、有流控。

  • 延迟极低,优于其他已知协议。

  • 节点间最大吞吐量与 TCP 相当,但在单机内(如进程间、容器间)的吞吐量优于 TCP。

强大的集群管理

  • 节点间链路 (Inter Node Links):在任意两个节点间维护一条或多条链路,保证数据完整性和监控对端节点的可用性。

  • 邻居发现 (Neighbor Discovery):通过以太网广播或 UDP 多播自动发现集群中的其他节点。

  • 高可扩展性:使用“重叠环监控算法”,可将集群规模扩展到 1000 个节点,同时保持 1-2 秒的故障发现时间。

灵活的配置

  • 单节点模式下无需任何配置。

  • 集群模式下,至少需要指定节点地址(Linux 4.17 之前)和要绑定的网络接口。可通过 tipc 工具进行更复杂的配置。

多语言支持

  • 用户 API 支持 C、Python、Perl、Ruby、D 和 Go 等多种编程语言。

3. 代码示例

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// tipc_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/tipc.h>

#define SERVICE_TYPE 18888
#define SERVICE_INST 101
#define BUF_SIZE 1024

int main(void) {
int sock;
struct sockaddr_tipc bind_addr;

// 1. 创建 TIPC socket(可靠数据报)
sock = socket(AF_TIPC, SOCK_RDM, 0);
if (sock < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

// 2. 构造绑定地址:服务类型 + 实例
memset(&bind_addr, 0, sizeof(bind_addr));
bind_addr.family = AF_TIPC;
bind_addr.addrtype = TIPC_SERVICE_ADDR;
bind_addr.addr.name.name.type = SERVICE_TYPE;
bind_addr.addr.name.name.instance = SERVICE_INST;

// 3. 绑定服务地址
if (bind(sock, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) {
perror("bind");
close(sock);
exit(EXIT_FAILURE);
}

printf(
"TIPC server listening on service (%d, %d)\n", SERVICE_TYPE,
SERVICE_INST);

// 4. 接收消息循环
char buffer[BUF_SIZE];
struct sockaddr_tipc src_addr;
struct iovec iov = {.iov_base = buffer, .iov_len = sizeof(buffer)};
struct msghdr msg = {
.msg_name = &src_addr,
.msg_namelen = sizeof(src_addr),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = NULL,
.msg_controllen = 0,
.msg_flags = 0};

while (1) {
ssize_t recv_bytes = recvmsg(sock, &msg, 0);
if (recv_bytes < 0) {
perror("recvmsg");
break;
}

// 确保字符串以 '\0' 结尾
buffer[recv_bytes] = '\0';

// 打印来源信息(可选)
if (src_addr.addrtype == TIPC_SERVICE_ADDR) {
printf(
"Received from service (%u, %u): %s\n",
src_addr.addr.name.name.type, src_addr.addr.name.name.instance,
buffer);
} else {
printf(
"Received from port (%u, %u): %s\n", src_addr.addr.id.ref,
src_addr.addr.id.node, buffer);
}
}

close(sock);
return 0;
}

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// tipc_sendmsg_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/tipc.h>

int main(void) {
int sock;
const char *message = "Hello from TIPC client!";
size_t msg_len = strlen(message) + 1; // 包含终止符

// 1. 创建 TIPC socket(使用可靠数据报模式)
sock = socket(AF_TIPC, SOCK_RDM, 0);
if (sock < 0) {
perror("socket(AF_TIPC)");
exit(EXIT_FAILURE);
}

// 2. 构造目标服务地址: type=18888, instance=101
struct sockaddr_tipc dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.family = AF_TIPC;
dest_addr.addrtype = TIPC_SERVICE_ADDR;
dest_addr.addr.name.name.type = 18888;
dest_addr.addr.name.name.instance = 101;

// 3. 准备 iovec(描述要发送的数据)
struct iovec iov = {.iov_base = (void *)message, .iov_len = msg_len};

// 4. 填充 msghdr 结构体
struct msghdr msg = {
.msg_name = &dest_addr,
.msg_namelen = sizeof(dest_addr),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = NULL,
.msg_controllen = 0,
.msg_flags = 0};

// 5. 发送消息
ssize_t sent_bytes = sendmsg(sock, &msg, 0);
if (sent_bytes < 0) {
perror("sendmsg");
close(sock);
exit(EXIT_FAILURE);
}

printf("Sent %zd bytes to service (18888, 101)\n", sent_bytes);

close(sock);
return 0;
}

编译运行

  • 查看是否加载 TIPC module
1
lsmod | grep tipc
  • 加载 TIPC module
1
sudo modprobe tipc

4. 实现原理

TIPC 内核如何响应这个请求?

步骤 1:查“名字表”(Name Table)

  • TIPC 内核维护一个全局服务注册表(每个节点都有副本)。

  • 当服务端执行 bind() 绑定 (18888, 101) 时,它会向全集群广播:“我提供此服务”,并记录在名字表中。

  • 客户端所在节点的名字表里就有这一条目,知道 (18888,101) 当前由 哪个 Node ID 提供。

步骤 2:路由到目标节点

  • TIPC 根据名字表查到目标 Node ID(比如 node=12345)。

  • 消息被封装成 TIPC 协议帧,通过与 node=12345 的可靠逻辑链路发送。

  • 这条链路有:

    • 序列号 + ACK 机制(防丢、防重、保序)
    • 心跳检测(快速发现节点宕机)

步骤 3:目标节点精确投递

  • 目标节点收到消息后,解析出目标服务地址 (18888,101)。

  • 查询本地名字表,找到绑定该地址的 socket。

  • 将数据放入该 socket 的接收缓冲区。

当服务端调用 recvmsg() 时,就能读到这条消息。