基于 Linux 系统的多链路互备份网络方案, 两条链路同时发送数据,并且通过 TUN(网络隧道)设备实现,以下是一个完整的设计和实现思路:
1. 方案概述
核心功能
- 使用 TUN 虚拟网络设备,作为逻辑网络接口。
- 通过两条物理链路发送数据(如
eth0
和eth1
),实现多路径传输。 - 使用 C 语言实现,将同一数据包通过两条链路发送。
- 在接收端根据数据标识去重。
工作流程
-
发送端:
- 应用程序将数据发送到 TUN 设备。
- TUN 设备捕获数据,交给 C 程序处理。
- C 程序将数据通过两条链路发送到接收端。
-
接收端:
- 两条链路接收到相同的数据。
- 数据在逻辑层聚合,并通过标识去重。
2. 环境准备
安装 TUN/TAP 支持
确保 Linux 系统支持 TUN/TAP:
sudo modprobe tun
检查是否加载成功:
ls /dev/net/tun
如果未加载,创建设备:
sudo mknod /dev/net/tun c 10 200
sudo chmod 0666 /dev/net/tun
安装必要的工具
开发环境需要:
sudo apt-get install build-essential libpcap-dev
3. 实现
3.1 客户端(发送端)
AppTun2Cli.c 代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <sys/time.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
// 定义常量
#define TUN_DEVICE "tun0"
#define BUFSIZE 2000
#define DEST_SERVER_IP "70.11.61.112"
#define SERVER_IP "70.11.61.222"
#define SERVER_PORT 55500
#define GATEWAY1 "192.168.0.1"
#define GATEWAY2 "192.168.10.1"
#define LOCAL_IP1 "192.168.0.194"
#define LOCAL_IP2 "192.168.10.100"
#define LOCAL_NET1 "192.168.0.0/24"
#define LOCAL_NET2 "192.168.10.0/24"
#define INTERFACE1 "eth0"
#define INTERFACE2 "enx000ec6374317"
#define VIRTUAL_IP "192.168.100.100/24"
#define VIRTUAL_GW "192.168.100.1"
#define PKG_TIMEOUT 5000 //数据包接收超时(毫秒)
// 全局变量
int tun_fd;
int udp_sock1, udp_sock2, netlink_sock;
struct sockaddr_in addr1, addr2, server_addr;
unsigned short packet_number = 0;
unsigned char if1_connected = 0,if2_connected = 0;
long long get_relative_time()
{
struct timeval tv;
struct timezone tz;
gettimeofday(&tv,&tz);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
int is_network_cable_connected(int sockfd,const char *interface_name) {
struct ifreq ifr;
// 将接口名称复制到 ifreq 结构
strncpy(ifr.ifr_name, interface_name, IFNAMSIZ - 1);
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
// 获取接口标志
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) {
perror("ioctl failed");
return -1;
}
// 检查 IFF_RUNNING 标志
if (ifr.ifr_flags & IFF_RUNNING) {
return 1; // 网线已连接
} else {
return 0; // 网线未连接
}
}
// 函数:创建和配置 TUN 设备
int create_tun_device() {
struct ifreq ifr;
int fd = open("/dev/net/tun", O_RDWR);
if (fd < 0) {
perror("Opening /dev/net/tun");
return fd;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TUN | IFF_NO_PI; // 使用 TUN 模式,无附加头
strncpy(ifr.ifr_name, TUN_DEVICE, IFNAMSIZ);
if (ioctl(fd, TUNSETIFF, &ifr) < 0) {
perror("ioctl(TUNSETIFF)");
close(fd);
return -1;
}
return fd;
}
// 线程函数:从 TUN 设备读取数据并通过 UDP 发送
void *tun_to_udp(void *arg) {
char buffer[BUFSIZE];
//unsigned char if1_connected = 0,if2_connected = 0;
while (1) {
int n = read(tun_fd, buffer + 2, BUFSIZE - 2); // 读取 IP 数据包
if (n < 0) {
perror("Reading from TUN device");
continue;
}
// 在数据包前加上编号
packet_number++;
buffer[0] = (packet_number >> 8) & 0xFF;
buffer[1] = packet_number & 0xFF;
// 发送到第一个网关
if(if1_connected)
sendto(udp_sock1, buffer, n + 2, 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
// 发送到第二个网关
if(if2_connected)
sendto(udp_sock2, buffer, n + 2, 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
}
}
// 线程函数:接收 UDP 数据并写回 TUN 设备
void *udp_to_tun(void *arg) {
unsigned char buffer[BUFSIZE];
unsigned char received_numbers[65536] = {0}; // 用于记录已接收的编号
unsigned char received_numbers_flags[2] = {0};
unsigned short number = 0;
long long last_recv_time = 0,cur_recv_time = 0;
fd_set read_fds;
int max_fd = (udp_sock1 > udp_sock2) ? udp_sock1 : udp_sock2;
while (1) {
FD_ZERO(&read_fds);
FD_SET(udp_sock1, &read_fds);
FD_SET(udp_sock2, &read_fds);
// 使用 select() 等待任意一个套接字有数据可读
int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (ret < 0) {
perror("select");
continue;
}
cur_recv_time = get_relative_time();
if(cur_recv_time - last_recv_time > PKG_TIMEOUT)
{
if(received_numbers_flags[0])
{
memset(received_numbers,0,0x8000);
received_numbers_flags[0] = 0;
}
if(received_numbers_flags[1])
{
memset(received_numbers + 0x8000,0,0x8000);
received_numbers_flags[1] = 0;
}
}
last_recv_time = cur_recv_time;
// 检查 udp_sock1 是否有数据
if (FD_ISSET(udp_sock1, &read_fds)) {
struct sockaddr_in from_addr;
socklen_t addrlen = sizeof(from_addr);
int n = recvfrom(udp_sock1, buffer, BUFSIZE, 0, (struct sockaddr *)&from_addr, &addrlen);
if (n >= 2) {
// 处理接收到的数据包
number = (unsigned short)(buffer[0] << 8) | buffer[1];
//printf("num:%d,cur:%d",number,cur_num);
if (!received_numbers[number]) {
received_numbers[number] = 1;
write(tun_fd, buffer + 2, n - 2); // 将数据写入 TUN 设备
}
}
}
// 检查 udp_sock2 是否有数据
if (FD_ISSET(udp_sock2, &read_fds)) {
struct sockaddr_in from_addr;
socklen_t addrlen = sizeof(from_addr);
int n = recvfrom(udp_sock2, buffer, BUFSIZE, 0, (struct sockaddr *)&from_addr, &addrlen);
if (n >= 2) {
// 处理接收到的数据包
number = (unsigned short)(buffer[0] << 8) | buffer[1];
//printf("num:%d,cur:%d",number,cur_num);
if (!received_numbers[number]) {
received_numbers[number] = 1;
write(tun_fd, buffer + 2, n - 2); // 将数据写入 TUN 设备
}
}
}
if (number < 0x8000)
{
received_numbers_flags[0] = 1;
if(received_numbers_flags[1])
{
memset(received_numbers + 0x8000,0,0x8000);
received_numbers_flags[1] = 0;
}
}
else
{
received_numbers_flags[1] = 1;
if(received_numbers_flags[0])
{
memset(received_numbers,0,0x8000);
received_numbers_flags[0] = 0;
}
}
}
}
void *netlink_detect(void *arg)
{
char buffer[8192];
while (1) {
int len = recv(netlink_sock, buffer, sizeof(buffer), 0);
if (len < 0) {
perror("recv failed");
close(netlink_sock);
continue;
}
// 解析 netlink 消息
struct nlmsghdr *nlh = (struct nlmsghdr *)buffer;
while (NLMSG_OK(nlh, len)) {
struct ifinfomsg *ifi = (struct ifinfomsg *)NLMSG_DATA(nlh);
// 获取接口索引并检查是否匹配指定接口
if (if_indextoname(ifi->ifi_index, (char[IFNAMSIZ]){0})) {
char iface_name[IFNAMSIZ];
if_indextoname(ifi->ifi_index, iface_name);
if (strcmp(iface_name, INTERFACE1) == 0) {
if (nlh->nlmsg_type == RTM_NEWLINK) {
if (ifi->ifi_flags & IFF_RUNNING) {
char buf[100] = {0};
system("ip route flush table 1");
sprintf(buf,"ip route add default via %s dev %s table 1",GATEWAY1,INTERFACE1);
system(buf);
memset(buf,0,100);
sprintf(buf,"ip route add %s dev %s table 1",LOCAL_NET1,INTERFACE1);
system(buf);
memset(buf,0,100);
sprintf(buf,"ip rule add from %s table 1",LOCAL_IP1);
system(buf);
if1_connected = 1;
printf("Network interface %s is connected\n", INTERFACE1);
} else {
if1_connected = 0;
printf("Network interface %s is disconnected\n", INTERFACE1);
}
}
}
if (strcmp(iface_name, INTERFACE2) == 0) {
if (nlh->nlmsg_type == RTM_NEWLINK) {
if (ifi->ifi_flags & IFF_RUNNING) {
char buf[100] = {0};
system("ip route flush table 2");
sprintf(buf,"ip route add default via %s dev %s table 2",GATEWAY2,INTERFACE2);
system(buf);
memset(buf,0,100);
sprintf(buf,"ip route add %s dev %s table 2",LOCAL_NET2,INTERFACE2);
system(buf);
memset(buf,0,100);
sprintf(buf,"ip rule add from %s table 2",LOCAL_IP2);
system(buf);
if2_connected = 1;
printf("Network interface %s is connected\n", INTERFACE2);
} else {
if2_connected = 0;
printf("Network interface %s is disconnected\n", INTERFACE2);
}
}
}
}
nlh = NLMSG_NEXT(nlh, len);
}
}
}
void write_file(const char* file_name,unsigned char* data,unsigned int offset,unsigned int len)
{
int fd = open(file_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
write(fd, data + offset,len);
fsync(fd);
close(fd);
}
int main() {
// 创建和配置 TUN 设备
tun_fd = create_tun_device();
if (tun_fd < 0) {
exit(1);
}
// 创建第一个 UDP 套接字
udp_sock1 = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_sock1 < 0) {
perror("Creating UDP socket1");
exit(1);
}
//设置Socket为非阻塞模式
int flags = fcntl(udp_sock1, F_GETFL, 0);
fcntl(udp_sock1, F_SETFL, flags | O_NONBLOCK);
// 绑定到第一个本地 IP 地址
struct sockaddr_in local_addr1;
memset(&local_addr1, 0, sizeof(local_addr1));
local_addr1.sin_family = AF_INET;
local_addr1.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, LOCAL_IP1, &local_addr1.sin_addr);
if (bind(udp_sock1, (struct sockaddr *)&local_addr1, sizeof(local_addr1)) < 0) {
perror("Binding UDP socket1");
exit(1);
}
// 创建第二个 UDP 套接字
udp_sock2 = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_sock2 < 0) {
perror("Creating UDP socket2");
exit(1);
}
//设置Socket为非阻塞模式
flags = fcntl(udp_sock2, F_GETFL, 0);
fcntl(udp_sock2, F_SETFL, flags | O_NONBLOCK);
// 绑定到第二个本地 IP 地址
struct sockaddr_in local_addr2;
memset(&local_addr2, 0, sizeof(local_addr2));
local_addr2.sin_family = AF_INET;
local_addr2.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, LOCAL_IP2, &local_addr2.sin_addr);
if (bind(udp_sock2, (struct sockaddr *)&local_addr2, sizeof(local_addr2)) < 0) {
perror("Binding UDP socket2");
exit(1);
}
// 设置目标服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
struct sockaddr_nl netlink_addr;
netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (netlink_sock < 0) {
perror("net link socket creation failed");
exit(1);
}
// 初始化 sockaddr_nl 结构
memset(&netlink_addr, 0, sizeof(netlink_addr));
netlink_addr.nl_family = AF_NETLINK;
netlink_addr.nl_groups = RTMGRP_LINK; // 监听网络接口的状态变化
// 绑定套接字
if (bind(netlink_sock, (struct sockaddr *)&netlink_addr, sizeof(netlink_addr)) < 0) {
perror("netlink_sock bind failed");
close(netlink_sock);
exit(1);
}
// 创建线程
pthread_t thread1, thread2, thread3;
pthread_create(&thread1, NULL, tun_to_udp, NULL);
pthread_create(&thread2, NULL, udp_to_tun, NULL);
pthread_create(&thread3, NULL, netlink_detect, NULL);
// 配置tun网卡IP地址
//ip addr add 192.168.100.100/24 dev tun0
//ip link set tun0 up
//iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
{
char buf[100] = {0};
sprintf(buf,"ip addr add %s dev %s",VIRTUAL_IP,TUN_DEVICE);
system(buf);
memset(buf,0,100);
sprintf(buf,"ip link set %s up",TUN_DEVICE);
system(buf);
memset(buf,0,100);
sprintf(buf,"iptables -t nat -A POSTROUTING -o %s -j MASQUERADE",TUN_DEVICE);
system(buf);
memset(buf,0,100);
sprintf(buf,"route add -host %s gw %s",DEST_SERVER_IP,VIRTUAL_GW);
system(buf);
}
int ret = is_network_cable_connected(udp_sock1,INTERFACE1);
if(ret == 1)
{
char buf[100] = {0};
system("ip route flush table 1");
sprintf(buf,"ip route add default via %s dev %s table 1",GATEWAY1,INTERFACE1);
system(buf);
memset(buf,0,100);
sprintf(buf,"ip route add %s dev %s table 1",LOCAL_NET1,INTERFACE1);
system(buf);
memset(buf,0,100);
sprintf(buf,"ip rule add from %s table 1",LOCAL_IP1);
system(buf);
if1_connected = 1;
}
ret = is_network_cable_connected(udp_sock2,INTERFACE2);
if(ret == 1)
{
char buf[100] = {0};
system("ip route flush table 2");
sprintf(buf,"ip route add default via %s dev %s table 2",GATEWAY2,INTERFACE2);
system(buf);
memset(buf,0,100);
sprintf(buf,"ip route add %s dev %s table 2",LOCAL_NET2,INTERFACE2);
system(buf);
memset(buf,0,100);
sprintf(buf,"ip rule add from %s table 2",LOCAL_IP2);
system(buf);
if2_connected = 1;
}
// 等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_join(thread3, NULL);
// 关闭套接字和 TUN 设备
close(udp_sock1);
close(udp_sock2);
close(tun_fd);
close(netlink_sock);
return 0;
}
3.2 服务端(接收端)
AppTun2Srv.c 代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <sys/time.h>
#define INTERFACE "eth0" //外网接口
#define TUN_DEVICE "tun0"
#define BUFSIZE 2000
#define UDP_PORT 55500
#define MAX_PEERS 2
#define VIRTUAL_IP "192.168.100.1/24"
#define PKG_TIMEOUT 5000 //数据包接收超时(毫秒)
// 用于存储 UDP 源 IP 和端口的结构体
typedef struct {
char ip[INET_ADDRSTRLEN];
int port;
} Peer;
Peer peers[MAX_PEERS];
int peer_count = 0;
pthread_mutex_t peer_list_mutex = PTHREAD_MUTEX_INITIALIZER;
int tun_fd;
int udp_sock;
unsigned short packet_number = 0;
// 函数:创建和配置 TUN 设备
int create_tun_device() {
struct ifreq ifr;
int fd = open("/dev/net/tun", O_RDWR);
if (fd < 0) {
perror("Opening /dev/net/tun");
return fd;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TUN | IFF_NO_PI; // 使用 TUN 模式,无附加头
strncpy(ifr.ifr_name, TUN_DEVICE, IFNAMSIZ);
if (ioctl(fd, TUNSETIFF, &ifr) < 0) {
perror("ioctl(TUNSETIFF)");
close(fd);
return -1;
}
return fd;
}
long long get_relative_time()
{
struct timeval tv;
struct timezone tz;
gettimeofday(&tv,&tz);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
// 检查源 IP 和端口是否已在列表中
int is_peer_in_list(const char *ip, int port) {
for (int i = 0; i < peer_count; ++i) {
if (strcmp(peers[i].ip, ip) == 0 && peers[i].port == port) {
return 1;
}
}
return 0;
}
// 添加新的源 IP 和端口到列表中
void add_peer_to_list(const char *ip, int port) {
pthread_mutex_lock(&peer_list_mutex);
if (!is_peer_in_list(ip, port)) {
if (peer_count < MAX_PEERS)
{
strncpy(peers[peer_count].ip, ip, INET_ADDRSTRLEN);
peers[peer_count].port = port;
peer_count++;
}
else if(peer_count == MAX_PEERS)
{
for (unsigned int i = 0; i < MAX_PEERS - 1; i++)
{
peers[i] = peers[i + 1];
}
strncpy(peers[MAX_PEERS - 1].ip, ip, INET_ADDRSTRLEN);
peers[MAX_PEERS - 1].port = port;
}
}
pthread_mutex_unlock(&peer_list_mutex);
}
// 线程函数:接收 UDP 数据并写入 TUN 设备
void *udp_to_tun(void *arg) {
unsigned char buffer[BUFSIZE];
unsigned char received_numbers[65536] = {0}; // 用于记录已接收的编号
unsigned char received_numbers_flags[2] = {0};
long long last_recv_time = 0,cur_recv_time = 0;
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
fd_set read_fds;
while (1) {
FD_ZERO(&read_fds);
FD_SET(udp_sock, &read_fds);
// 使用 select() 套接字有数据可读
int ret = select(udp_sock + 1, &read_fds, NULL, NULL, NULL);
if (ret < 0) {
perror("select");
continue;
}
int len = recvfrom(udp_sock, buffer, BUFSIZE, 0, (struct sockaddr *)&client_addr, &addr_len);
if (len < 2) {
perror("Receive failed");
continue;
}
cur_recv_time = get_relative_time();
if(cur_recv_time - last_recv_time > PKG_TIMEOUT)
{
if(received_numbers_flags[0])
{
memset(received_numbers,0,0x8000);
received_numbers_flags[0] = 0;
}
if(received_numbers_flags[1])
{
memset(received_numbers + 0x8000,0,0x8000);
received_numbers_flags[1] = 0;
}
}
last_recv_time = cur_recv_time;
// 提取编号
unsigned short number = (unsigned short)(buffer[0] << 8) | buffer[1];
// 获取源 IP 和端口
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
int client_port = ntohs(client_addr.sin_port);
// 添加源 IP 和端口到列表
add_peer_to_list(client_ip, client_port);
if (received_numbers[number]) {
continue; // 已经收到过,丢弃
}
// 标记为已收到
received_numbers[number] = 1;
// 将数据包的前两字节移除,并写入 TUN 设备
write(tun_fd, buffer + 2, len - 2);
if (number < 0x8000)
{
received_numbers_flags[0] = 1;
if(received_numbers_flags[1])
{
memset(received_numbers + 0x8000,0,0x8000);
received_numbers_flags[1] = 0;
}
}
else
{
received_numbers_flags[1] = 1;
if(received_numbers_flags[0])
{
memset(received_numbers,0,0x8000);
received_numbers_flags[0] = 0;
}
}
}
}
// 线程函数:从 TUN 设备读取数据并通过 UDP 发送
void *tun_to_udp(void *arg) {
unsigned char buffer[BUFSIZE];
while (1) {
int len = read(tun_fd, buffer + 2, BUFSIZE - 2); // 读取 IP 数据包
if (len < 0) {
perror("Reading from TUN device");
continue;
}
// 在数据包前加上编号
packet_number++;
buffer[0] = (packet_number >> 8) & 0xFF;
buffer[1] = packet_number & 0xFF;
// 发送数据包到所有记录的源 IP 和端口
pthread_mutex_lock(&peer_list_mutex);
for (int i = 0; i < peer_count; ++i) {
struct sockaddr_in peer_addr;
memset(&peer_addr, 0, sizeof(peer_addr));
peer_addr.sin_family = AF_INET;
peer_addr.sin_port = htons(peers[i].port);
inet_pton(AF_INET, peers[i].ip, &peer_addr.sin_addr);
sendto(udp_sock, buffer, len + 2, 0, (struct sockaddr *)&peer_addr, sizeof(peer_addr));
}
pthread_mutex_unlock(&peer_list_mutex);
}
}
void write_file(const char* file_name,unsigned char* data,unsigned int offset,unsigned int len)
{
int fd = open(file_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
write(fd, data + offset,len);
fsync(fd);
close(fd);
}
int main() {
// 创建和配置 TUN 设备
tun_fd = create_tun_device();
if (tun_fd < 0) {
exit(1);
}
// 创建 UDP 套接字
udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_sock < 0) {
perror("Creating UDP socket");
exit(1);
}
//设置Socket为非阻塞模式
int flags = fcntl(udp_sock, F_GETFL, 0);
fcntl(udp_sock, F_SETFL, flags | O_NONBLOCK);
// 绑定套接字到指定端口
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(UDP_PORT);
if (bind(udp_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(udp_sock);
exit(1);
}
// 创建线程
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, udp_to_tun, NULL);
pthread_create(&thread2, NULL, tun_to_udp, NULL);
// 配置tun网卡IP地址
{
char buf[100] = {0};
sprintf(buf,"ip addr add %s dev %s",VIRTUAL_IP,TUN_DEVICE);
system(buf);
memset(buf,0,100);
sprintf(buf,"ip link set %s up",TUN_DEVICE);
system(buf);
memset(buf,0,100);
sprintf(buf,"iptables -t nat -A POSTROUTING -o %s -j MASQUERADE",INTERFACE);
system(buf);
}
// 等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
// 关闭套接字和 TUN 设备
close(udp_sock);
close(tun_fd);
return 0;
}
4. 使用方法
4.1 编译代码
使用以下命令编译:
gcc AppTun2Cli.c -lpthread -o AppTun2Cli
gcc AppTun2Srv.c -lpthread -o AppTun2Srv
4.2 服务文件
客户端服务 AppTun2Cli.service:
[Unit]
Description=AppTun2Cli service
After=network.target
[Service]
WorkingDirectory=/root/2net
Restart=on-failure
RestartSec=5
ExecStart=/root/2net/AppTun2Cli
User=root
[Install]
WantedBy=multi-user.target
服务端服务 AppTun2Srv.service:
[Unit]
Description=AppTun2Srv service
After=network.target
[Service]
WorkingDirectory=/root/2net
Restart=on-failure
RestartSec=5
ExecStart=/root/2net/AppTun2Srv
User=root
[Install]
WantedBy=multi-user.target
把service文件放到/etc/systemd/system目录下
4.2 启动程序
运行服务:
#启动客户端程序
sudo service AppTun2Cli start
#启动服务端程序
sudo service AppTun2Srv start
停止服务:
#停止客户端程序
sudo service AppTun2Cli stop
#停止服务端程序
sudo service AppTun2Srv stop
6. 注意事项
- TUN/TAP 权限:程序需要以
root
权限运行。 - 链路带宽和延迟:两条链路的带宽和延迟差异可能导致乱序,接收端需要处理乱序包。
- 错误处理:增强对异常的处理(如链路故障检测)。
- 去重优化:使用唯一标识符(如序列号)替代完整数据进行比较,提高效率。
通过以上实现,可以构建一个支持多链路传输和冗余发送的高可用网络方案!