Linux网络编程试验方案
- TCP
- UDP
- 具体实验
-
- 实验步骤分析
- [Step 1:完全不写网络](#Step 1:完全不写网络)
- [Step 2:完全不写按键](#Step 2:完全不写按键)
- [Step 3:把真按键接进来](#Step 3:把真按键接进来)
- [Step 4:TCP 跑稳后,再动 UDP](#Step 4:TCP 跑稳后,再动 UDP)
TCP
server.c
服务器端
c
/*
* 基于Linux的TCP服务器端程序
* 功能:创建TCP服务端,监听指定端口,接收客户端连接并持续读取客户端发送的消息
* 核心流程:socket() -> bind() -> listen() -> accept() -> recv()
*/
// 网络编程相关头文件
#include <sys/types.h> // 基本系统数据类型,如pid_t, size_t等
#include <sys/socket.h> // socket核心函数和数据结构
#include <string.h> // 字符串处理函数(memset等)
#include <netinet/in.h> // 定义IPv4地址结构sockaddr_in
#include <arpa/inet.h> // 提供IP地址转换函数(inet_ntoa等)
#include <unistd.h> // 提供通用的UNIX系统调用(close, fork等)
#include <stdio.h> // 标准输入输出
#include <signal.h> // 信号处理(处理子进程退出)
/* 服务器核心流程:
* socket - 创建套接字,获取文件描述符
* bind - 将套接字绑定到指定IP和端口
* listen - 开始监听客户端连接请求
* accept - 接受客户端连接,创建新的通信套接字
* send/recv - 与客户端进行数据收发
*/
// 宏定义 - 服务器配置参数
#define SERVER_PORT 8888 // 服务器监听的端口号
#define BACKLOG 10 // listen的最大等待队列长度(未处理的连接数)
int main(int argc, char **argv)
{
int iSocketServer; // 服务器监听套接字的文件描述符
int iSocketClient; // 客户端通信套接字的文件描述符(每个客户端一个)
struct sockaddr_in tSocketServerAddr; // 服务器地址结构体(IPv4)
struct sockaddr_in tSocketClientAddr; // 客户端地址结构体(IPv4)
int iRet; // 函数返回值,用于检查调用是否成功
int iAddrLen; // 地址结构体的长度,传给accept使用
int iRecvLen; // 接收数据的长度
unsigned char ucRecvBuf[1000]; // 接收数据的缓冲区(最大999字节+1字节结束符)
int iClientNum = -1; // 客户端计数器(从0开始),用于标识不同客户端
// 设置信号处理:忽略子进程退出信号(SIGCHLD)
// 避免子进程退出后变成僵尸进程,由系统自动回收子进程资源
signal(SIGCHLD, SIG_IGN);
/* 1. 创建套接字(socket)
* AF_INET:使用IPv4协议族
* SOCK_STREAM:使用TCP协议(面向连接、可靠传输)
* 0:使用默认的协议(TCP)
* 返回值:成功返回套接字文件描述符,失败返回-1
*/
iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!\n"); // 创建套接字失败
return -1;
}
/* 2. 初始化服务器地址结构体 */
tSocketServerAddr.sin_family = AF_INET; // 地址族:IPv4
tSocketServerAddr.sin_port = htons(SERVER_PORT); // 端口号:主机字节序转网络字节序(大端)
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡的IP地址(0.0.0.0)
memset(tSocketServerAddr.sin_zero, 0, 8); // 填充0,使结构体大小与sockaddr一致
/* 3. 绑定套接字(bind)
* 将创建的套接字与指定的IP和端口绑定
* 参数1:服务器套接字描述符
* 参数2:通用地址结构体指针(强转)
* 参数3:地址结构体长度
* 返回值:成功返回0,失败返回-1
*/
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\n"); // 绑定失败(常见原因:端口被占用、无权限)
return -1;
}
/* 4. 开始监听(listen)
* 将套接字设置为监听模式,准备接收客户端连接
* 参数1:服务器套接字描述符
* 参数2:等待队列长度(最多同时有BACKLOG个未处理的连接)
* 返回值:成功返回0,失败返回-1
*/
iRet = listen(iSocketServer, BACKLOG);
if (-1 == iRet)
{
printf("listen error!\n"); // 监听失败
return -1;
}
/* 5. 主循环:持续等待并处理客户端连接 */
while (1)
{
iAddrLen = sizeof(struct sockaddr); // 初始化地址长度
/* 5.1 接受客户端连接(accept)
* 阻塞等待客户端连接请求,成功后创建新的通信套接字
* 参数1:服务器监听套接字描述符
* 参数2:输出参数,存储客户端的地址信息
* 参数3:地址结构体长度的指针(输入输出)
* 返回值:成功返回新的通信套接字描述符,失败返回-1
*/
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (-1 != iSocketClient) // 成功接收到客户端连接
{
iClientNum++; // 客户端计数器自增(第一个客户端为0)
// 打印客户端信息:编号 + IP地址(网络字节序转字符串)
printf("Get connect from client %d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
/* 5.2 创建子进程处理客户端通信
* fork():创建子进程,父进程返回子进程PID,子进程返回0
* 子进程负责与客户端通信,父进程继续等待新的连接
*/
if (!fork()) // 子进程分支(fork返回0)
{
/* 子进程:专门处理当前客户端的消息接收 */
while (1)
{
/* 接收客户端数据(recv)
* 参数1:客户端通信套接字描述符
* 参数2:接收缓冲区
* 参数3:缓冲区最大长度(999,留1字节给结束符)
* 参数4:接收模式(0:阻塞模式)
* 返回值:成功返回接收的字节数,连接关闭返回0,失败返回-1
*/
iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0) // 接收失败或客户端关闭连接
{
close(iSocketClient); // 关闭客户端通信套接字
return -1; // 子进程退出
}
else // 成功接收数据
{
ucRecvBuf[iRecvLen] = '\0'; // 添加字符串结束符,方便打印
// 打印客户端发送的消息
printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
}
}
}
// 父进程:关闭客户端套接字(子进程会继承该描述符,不影响)
close(iSocketClient);
}
}
// 关闭服务器监听套接字(实际主循环不会退出,此代码仅作形式)
close(iSocketServer);
return 0;
}
client.c
客户端
c
/*
* 基于Linux的TCP客户端程序
* 功能:连接指定IP的TCP服务器,从标准输入读取字符串并发送给服务器
* 核心流程:socket() -> connect() -> send()
*/
// 网络编程相关头文件
#include <sys/types.h> // 基本系统数据类型(如pid_t、size_t)
#include <sys/socket.h> // socket核心函数和数据结构(socket/connect/send等)
#include <string.h> // 字符串处理函数(strlen/memset/inet_aton等)
#include <netinet/in.h> // 定义IPv4地址结构sockaddr_in
#include <arpa/inet.h> // 提供IP地址转换函数(inet_aton)
#include <unistd.h> // 通用UNIX系统调用(close)
#include <stdio.h> // 标准输入输出(fgets/printf)
/* 客户端核心流程:
* socket - 创建套接字,获取通信文件描述符
* connect - 连接到指定IP和端口的TCP服务器
* send/recv - 与服务器进行数据收发
*/
// 宏定义 - 服务器配置参数
#define SERVER_PORT 8888 // 要连接的服务器端口号(需与服务器端保持一致)
int main(int argc, char **argv)
{
int iSocketClient; // 客户端通信套接字的文件描述符
struct sockaddr_in tSocketServerAddr; // 服务器地址结构体(存储要连接的服务器IP和端口)
int iRet; // 函数返回值,用于检查调用是否成功
unsigned char ucSendBuf[1000]; // 发送数据的缓冲区(最大999字节+1字节结束符)
int iSendLen; // 实际发送的字节数
/* 1. 校验命令行参数:必须传入服务器IP地址 */
if (argc != 2)
{
// 提示用户正确的使用方式(argv[0]是程序自身名称)
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]); // 示例:./client 192.168.1.100
return -1;
}
/* 2. 创建套接字(socket)
* AF_INET:使用IPv4协议族
* SOCK_STREAM:使用TCP协议(面向连接、可靠传输)
* 0:使用默认的协议(TCP)
* 返回值:成功返回套接字文件描述符,失败返回-1
*/
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
// 注:此处可增加socket创建失败的判断(原代码未加,补充注释提醒)
if (-1 == iSocketClient)
{
printf("socket create error!\n");
return -1;
}
/* 3. 初始化服务器地址结构体 */
tSocketServerAddr.sin_family = AF_INET; // 地址族:IPv4
tSocketServerAddr.sin_port = htons(SERVER_PORT); // 服务器端口:主机字节序转网络字节序(大端)
// tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; // 客户端无需绑定自身IP,注释掉
/* 将命令行传入的服务器IP字符串(如"192.168.1.100")转换为网络字节序的整数
* inet_aton:成功返回1,失败返回0(如IP格式错误)
*/
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n"); // 服务器IP地址格式无效(如192.168.1.256)
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8); // 填充0,使结构体大小与sockaddr一致
/* 4. 连接服务器(connect)
* 向指定的服务器IP和端口发起TCP连接(三次握手)
* 参数1:客户端套接字描述符
* 参数2:通用地址结构体指针(强转),存储服务器地址信息
* 参数3:地址结构体长度
* 返回值:成功返回0,失败返回-1(如服务器未启动、IP/端口错误、网络不通)
*/
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\n"); // 连接服务器失败
close(iSocketClient); // 失败时关闭套接字,避免资源泄漏
return -1;
}
/* 5. 主循环:持续从标准输入读取数据并发送给服务器 */
while (1)
{
/* 从标准输入(键盘)读取字符串到缓冲区
* fgets:最多读取999字节(缓冲区大小-1),自动在末尾加'\0'
* 回车换行符(\n)会被一并读取,用户输入回车即触发发送
* 返回值:成功返回缓冲区指针,失败/EOF返回NULL
*/
if (fgets(ucSendBuf, 999, stdin))
{
/* 发送数据到服务器(send)
* 参数1:客户端通信套接字描述符
* 参数2:要发送的缓冲区
* 参数3:要发送的字节数(strlen忽略'\0',只发送有效字符)
* 参数4:发送模式(0:阻塞模式)
* 返回值:成功返回实际发送的字节数,失败返回-1
*/
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if (iSendLen <= 0) // 发送失败(如服务器断开连接、网络异常)
{
printf("send error or server disconnect!\n");
close(iSocketClient); // 关闭套接字释放资源
return -1;
}
// 可选:打印发送成功的提示(原代码未加,补充注释)
// printf("Send to server: %s", ucSendBuf);
}
}
// 关闭客户端套接字(实际主循环不会退出,此代码仅作形式)
close(iSocketClient);
return 0;
}
UDP
server.c
c
/*
* 基于Linux的UDP服务器端程序
* 功能:创建UDP服务端,监听指定端口,接收任意客户端发送的UDP数据包并打印
* 核心流程:socket() -> bind() -> recvfrom()
* 关键特性:UDP是无连接、不可靠的传输协议,无需listen/accept,无需创建子进程
*/
// 网络编程相关头文件
#include <sys/types.h> // 基本系统数据类型(pid_t、size_t等)
#include <sys/socket.h> // socket核心函数和数据结构(socket/bind/recvfrom等)
#include <string.h> // 字符串处理函数(memset/strlen等)
#include <netinet/in.h> // 定义IPv4地址结构sockaddr_in
#include <arpa/inet.h> // 提供IP地址转换函数(inet_ntoa)
#include <unistd.h> // 通用UNIX系统调用(close)
#include <stdio.h> // 标准输入输出(printf)
#include <signal.h> // 信号处理(原代码保留但未使用,注释说明)
/* UDP服务器核心流程(与TCP的核心区别):
* socket - 创建UDP套接字(SOCK_DGRAM)
* bind - 将套接字绑定到指定IP和端口(TCP也需要,但UDP无需后续listen/accept)
* recvfrom/sendto - 接收/发送UDP数据包(替代TCP的recv/send,需指定对方地址)
*/
// 宏定义 - 服务器配置参数
#define SERVER_PORT 8888 // 服务器监听的UDP端口号
int main(int argc, char **argv)
{
int iSocketServer; // UDP服务器套接字的文件描述符(UDP只有一个套接字,无需区分监听/通信)
int iSocketClient; // 原TCP代码残留变量,UDP无需使用,注释说明
struct sockaddr_in tSocketServerAddr; // 服务器地址结构体(存储绑定的IP和端口)
struct sockaddr_in tSocketClientAddr; // 客户端地址结构体(接收数据包时,存储发送方的IP和端口)
int iRet; // 函数返回值,用于检查调用是否成功
int iAddrLen; // 地址结构体的长度,传给recvfrom使用
int iRecvLen; // 接收UDP数据包的实际长度
unsigned char ucRecvBuf[1000]; // 接收数据的缓冲区(最大999字节+1字节结束符)
int iClientNum = -1; // 原TCP代码残留变量(客户端计数器),UDP无需使用,注释说明
/* 1. 创建UDP套接字(socket)
* AF_INET:使用IPv4协议族
* SOCK_DGRAM:使用UDP协议(无连接、不可靠、面向数据报)
* 0:使用默认的UDP协议
* 返回值:成功返回套接字文件描述符,失败返回-1
* 核心区别:TCP用SOCK_STREAM,UDP用SOCK_DGRAM
*/
iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!\n"); // 创建UDP套接字失败
return -1;
}
/* 2. 初始化服务器地址结构体 */
tSocketServerAddr.sin_family = AF_INET; // 地址族:IPv4
tSocketServerAddr.sin_port = htons(SERVER_PORT); // 端口号:主机字节序转网络字节序(大端)
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡的IP地址(0.0.0.0)
memset(tSocketServerAddr.sin_zero, 0, 8); // 填充0,使结构体大小与sockaddr一致
/* 3. 绑定套接字(bind)
* 将UDP套接字绑定到指定的IP和端口(UDP必须绑定,否则系统随机分配端口)
* 参数与TCP的bind完全一致,但UDP绑定后无需listen/accept
* 返回值:成功返回0,失败返回-1
*/
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\n"); // 绑定失败(常见原因:端口被占用、无权限)
return -1;
}
/* 4. 主循环:持续接收UDP数据包 */
while (1)
{
iAddrLen = sizeof(struct sockaddr); // 初始化地址结构体长度
/* 4.1 接收UDP数据包(recvfrom)
* 核心作用:从UDP套接字读取数据包,并获取发送方(客户端)的地址信息
* 参数1:服务器套接字描述符
* 参数2:接收缓冲区
* 参数3:缓冲区最大长度(999,预留1字节给\0)
* 参数4:接收模式(0:阻塞模式,无数据时等待)
* 参数5:输出参数,存储发送方的地址信息(客户端IP+端口)
* 参数6:地址结构体长度的指针(输入输出)
* 返回值:成功返回接收的字节数,失败返回-1
* 核心区别:TCP用recv,UDP用recvfrom(需获取对方地址)
*/
iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0) // 成功接收到UDP数据包
{
ucRecvBuf[iRecvLen] = '\0'; // 添加字符串结束符,方便打印
// 打印客户端信息:IP地址(网络字节序转字符串) + 发送的消息
printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
}
// 注:UDP无连接,无需处理客户端断开,也无需创建子进程
}
// 关闭服务器套接字(实际主循环不会退出,此代码仅作形式)
close(iSocketServer);
return 0;
}

client.c
c
/*
* 基于Linux的UDP客户端程序(特殊版本:提前调用connect)
* 功能:向指定IP和端口的UDP服务器发送数据,通过connect绑定固定服务器地址,可使用send替代sendto
* 核心流程:socket() -> connect() -> send()
* 关键特性:UDP本身无连接,但可通过connect绑定唯一服务器地址,简化收发逻辑
*/
// 网络编程相关头文件
#include <sys/types.h> // 基本系统数据类型(pid_t、size_t等)
#include <sys/socket.h> // socket核心函数和数据结构(socket/connect/send等)
#include <string.h> // 字符串处理函数(strlen/memset/inet_aton等)
#include <netinet/in.h> // 定义IPv4地址结构sockaddr_in
#include <arpa/inet.h> // 提供IP地址转换函数(inet_aton)
#include <unistd.h> // 通用UNIX系统调用(close)
#include <stdio.h> // 标准输入输出(fgets/printf)
/* UDP客户端核心流程(本版本特殊点):
* socket - 创建UDP套接字(SOCK_DGRAM)
* connect - 非TCP的"建立连接",仅绑定固定的服务器地址(UDP无三次握手)
* send - 因提前connect绑定地址,可替代sendto(无需每次指定服务器地址)
* 对比普通UDP客户端:普通UDP用sendto,本版本用connect+send简化操作
*/
// 宏定义 - 服务器配置参数
#define SERVER_PORT 8888 // 要发送数据的UDP服务器端口号
int main(int argc, char **argv)
{
int iSocketClient; // 客户端UDP套接字的文件描述符
struct sockaddr_in tSocketServerAddr; // 服务器地址结构体(存储要发送的目标IP和端口)
int iRet; // 函数返回值,用于检查调用是否成功
unsigned char ucSendBuf[1000]; // 发送数据的缓冲区(最大999字节+1字节结束符)
int iSendLen; // 实际发送的字节数
/* 1. 校验命令行参数:必须传入服务器IP地址 */
if (argc != 2)
{
// 提示用户正确的使用方式(argv[0]是程序自身名称)
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]); // 示例:./udp_client 192.168.1.100
return -1;
}
/* 2. 创建UDP套接字(socket)
* AF_INET:使用IPv4协议族
* SOCK_DGRAM:使用UDP协议(无连接、不可靠、面向数据报)
* 0:使用默认的UDP协议
* 返回值:成功返回套接字文件描述符,失败返回-1
* 核心区别:TCP用SOCK_STREAM,UDP用SOCK_DGRAM
*/
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
// 补充:原代码未加socket创建失败判断,实际使用建议添加
if (-1 == iSocketClient)
{
printf("socket create error!\n");
return -1;
}
/* 3. 初始化服务器地址结构体 */
tSocketServerAddr.sin_family = AF_INET; // 地址族:IPv4
tSocketServerAddr.sin_port = htons(SERVER_PORT); // 服务器端口:主机字节序转网络字节序(大端)
// tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; // 客户端无需绑定自身IP,注释掉
/* 将命令行传入的服务器IP字符串(如"192.168.1.100")转换为网络字节序的整数
* inet_aton:成功返回1,失败返回0(如IP格式错误)
*/
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n"); // 服务器IP地址格式无效(如192.168.1.256)
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8); // 填充0,使结构体大小与sockaddr一致
/* 4. 调用connect(UDP的特殊用法)
* 注意:UDP的connect不是"建立连接"(无三次握手),仅做两件事:
* 1) 绑定该套接字的默认目标地址为tSocketServerAddr(后续send无需指定地址);
* 2) 该套接字仅能接收来自此服务器的数据包(其他客户端的包会被丢弃);
* 参数与TCP的connect完全一致,但逻辑完全不同(TCP是建立连接,UDP是绑定地址)
* 返回值:成功返回0,失败返回-1(如服务器地址不可达不会报错,UDP无连接校验)
*/
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\n"); // UDP的connect失败(如地址格式错误)
close(iSocketClient); // 失败时关闭套接字,避免资源泄漏
return -1;
}
/* 5. 主循环:持续从标准输入读取数据并发送给服务器 */
while (1)
{
/* 从标准输入(键盘)读取字符串到缓冲区
* fgets:最多读取999字节(缓冲区大小-1),自动在末尾加'\0'
* 回车换行符(\n)会被一并读取,用户输入回车即触发发送
* 返回值:成功返回缓冲区指针,失败/EOF返回NULL
*/
if (fgets(ucSendBuf, 999, stdin))
{
/* 发送数据到服务器(send)
* 因提前调用connect绑定了服务器地址,此处可直接用send(替代UDP常规的sendto)
* 参数1:客户端UDP套接字描述符
* 参数2:要发送的缓冲区
* 参数3:要发送的字节数(strlen忽略'\0',只发送有效字符)
* 参数4:发送模式(0:阻塞模式)
* 返回值:成功返回实际发送的字节数,失败返回-1
* 对比普通UDP:普通UDP需用sendto(&tSocketServerAddr, ...),本版本简化为send
*/
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if (iSendLen <= 0) // 发送失败(如套接字错误、系统资源不足)
{
printf("send error!\n");
close(iSocketClient); // 关闭套接字释放资源
return -1;
}
// 可选:打印发送成功的提示
// printf("Send to server: %s", ucSendBuf);
}
}
// 关闭客户端套接字(实际主循环不会退出,此代码仅作形式)
close(iSocketClient);
return 0;
}
对比

具体实验
实验步骤分析
✅ Step 1:完全不写网络
👉 只验证:
按键 → 消息队列 → 打印
能看到:
KEY0 down → write queue → read queue OK
这一步不通,后面全白搭
✅ Step 2:完全不写按键
👉 写一个"假事件":
每 1 秒往消息队列塞一个假按键事件
让网络线程:
• 发心跳
• 发事件
只验证网络是否通
✅ Step 3:把真按键接进来
• 把 Step1 的按键检测
• 接到 Step2 的网络线程
这一步只是"接线",不加新逻辑
✅ Step 4:TCP 跑稳后,再动 UDP
⚠️ UDP 一定最后做
TCP 通了:
• 说明你的
o 线程
o 队列
o 报文
o 心跳
o 上位机解析
全部是对的
UDP 只是"换传输方式",不是新功能。
Step 1:完全不写网络

msg_def.h:定义消息队列的统一规则(共享 key、消息类型、消息结构体格式),是发送 / 接收进程的通信约定;
key_process.c:监听硬件按键(KEY0),按下时按约定格式封装消息并发送到消息队列;
queue_recv.c:持续从消息队列读取指定类型的按键消息,打印接收内容。
msg_def.h
c
#ifndef __MSG_DEF_H__
#define __MSG_DEF_H__
#include <sys/types.h>
#define MSG_KEY 0x1234 // 固定 key,两个进程必须一致
#define MSG_TYPE_KEY 1 // 消息类型:按键事件
#define MSG_TEXT_LEN 32
struct msgbuf {
long mtype; // 必须是 long
char mtext[MSG_TEXT_LEN]; // 消息内容
};
#endif
key_process.c
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/input.h>
#include <string.h>
#include <sys/msg.h>
#include "msg_def.h"
#define KEY_DEV "/dev/input/event1"
#define TARGET_KEY_CODE 3 // 原来用的 KEY0
int main(void)
{
int msgid = msgget(MSG_KEY, IPC_CREAT | 0666);
if (msgid < 0) {
perror("msgget");
return -1;
}
int fd = open(KEY_DEV, O_RDONLY);
if (fd < 0) {
perror("open key device");
return -1;
}
struct input_event ev;
struct msgbuf msg;
msg.mtype = MSG_TYPE_KEY;
printf("[key_process] 开始监听按键...\n");
while (1) {
int ret = read(fd, &ev, sizeof(ev));
if (ret != sizeof(ev))
continue;
if (ev.type == EV_KEY &&
ev.code == TARGET_KEY_CODE &&
ev.value == 1) // 只认"按下"
{
strcpy(msg.mtext, "KEY0_DOWN");
if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) < 0) {
perror("msgsnd");
} else {
printf("[key_process] KEY0 按下 → 写入消息队列\n");
}
}
}
close(fd);
return 0;
}
queue_recv.c
c
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include "msg_def.h"
int main(void)
{
int msgid = msgget(MSG_KEY, IPC_CREAT | 0666);
if (msgid < 0) {
perror("msgget");
return -1;
}
struct msgbuf msg;
printf("[queue_recv] 等待接收消息...\n");
while (1) {
if (msgrcv(msgid, &msg, sizeof(msg.mtext),
MSG_TYPE_KEY, 0) < 0) {
perror("msgrcv");
continue;
}
printf("[queue_recv] 收到消息:%s\n", msg.mtext);
}
return 0;
}

Step 2:完全不写按键
fake_event.c → 消息队列(key=ftok("/tmp",'k')) → net_sender.c(读取) → TCP 网络 → net_server.c(解析/打印)
fake_event.c
模拟按键事件生产者,通过 ftok 生成消息队列 key,周期性构造带时间戳、校验位的 KEY0 按下消息,写入 System V 消息队列,无需依赖真实硬件,用于测试。
net_sender.c
消息队列与 TCP 网络的桥接程序,兼具两个核心角色:
• 消息队列消费者:非阻塞读取 fake_event.c 写入的按键消息;
• TCP 客户端:连接指定服务器,每秒发送心跳包保活,同时将读取到的按键消息转发至服务器。
net_server.c
TCP 服务端程序,监听指定端口并接收客户端连接,解析客户端发来的两种数据:6 字节心跳包(校验并打印时间戳)、8 字节按键事件包(校验并解析按键信息),是整个链路的最终数据接收端。
msg_def.h
通信格式头文件,统一定义按键消息结构体、消息类型宏、异或校验函数,保证 fake_event.c 和 net_sender.c 之间的消息格式一致,是跨程序通信的 "约定标准"。
msg_def.h
c
#ifndef MSG_DEF_H
#define MSG_DEF_H
#include <stdint.h>
#include <time.h>
#define MSG_TYPE_KEY 2
/* 按键事件消息结构(System V 消息队列) */
struct key_msg {
long mtype; // 消息类型,必须是 long
uint8_t type; // 0x02 = 按键事件
uint8_t key_id; // 0x00 = KEY0
uint8_t event; // 0x01 = 按下
uint32_t ts; // 时间戳(秒)
uint8_t checksum; // 异或校验
};
/* 简单异或校验 */
static inline uint8_t calc_xor(uint8_t *buf, int len)
{
uint8_t c = 0;
for (int i = 0; i < len; i++)
c ^= buf[i];
return c;
}
#endif
fake_event.c
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "msg_def.h"
int main(void)
{
key_t key = ftok("/tmp", 'k');
if (key == -1) {
perror("ftok");
return 1;
}
int msqid = msgget(key, IPC_CREAT | 0666);
if (msqid < 0) {
perror("msgget");
return 1;
}
printf("[fake_event] started, msqid=%d\n", msqid);
while (1) {
struct key_msg msg;
msg.mtype = MSG_TYPE_KEY;
msg.type = 0x02;
msg.key_id = 0x00;
msg.event = 0x01;
msg.ts = time(NULL);
msg.checksum = calc_xor(
(uint8_t *)&msg.type,
sizeof(msg) - sizeof(long) - 1
);
if (msgsnd(msqid, &msg,
sizeof(msg) - sizeof(long),
0) < 0) {
perror("msgsnd");
} else {
printf("[fake_event] fake key event queued\n");
}
sleep(1);
}
return 0;
}
net_sender.c
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <time.h>
#include "msg_def.h"
static int tcp_connect(const char *ip, int port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return -1;
}
struct sockaddr_in srv;
memset(&srv, 0, sizeof(srv));
srv.sin_family = AF_INET;
srv.sin_port = htons(port);
inet_pton(AF_INET, ip, &srv.sin_addr);
if (connect(sock, (struct sockaddr *)&srv, sizeof(srv)) < 0) {
perror("connect");
close(sock);
return -1;
}
printf("[net_sender] TCP connected to %s:%d\n", ip, port);
return sock;
}
static void send_heartbeat(int sock)
{
uint8_t buf[6];
uint32_t ts = time(NULL);
buf[0] = 0x01;
memcpy(&buf[1], &ts, 4);
buf[5] = calc_xor(buf, 5);
send(sock, buf, sizeof(buf), 0);
printf("[net_sender] heartbeat sent\n");
}
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("Usage: %s <server_ip> <port>\n", argv[0]);
return 1;
}
const char *server_ip = argv[1];
int port = atoi(argv[2]);
key_t key = ftok("/tmp", 'k');
int msqid = msgget(key, IPC_CREAT | 0666);
if (msqid < 0) {
perror("msgget");
return 1;
}
int sock = tcp_connect(server_ip, port);
if (sock < 0)
return 1;
time_t last_hb = 0;
while (1) {
time_t now = time(NULL);
/* 心跳 */
if (now - last_hb >= 1) {
send_heartbeat(sock);
last_hb = now;
}
/* 读取假按键事件 */
struct key_msg msg;
if (msgrcv(msqid, &msg,
sizeof(msg) - sizeof(long),
MSG_TYPE_KEY,
IPC_NOWAIT) > 0) {
send(sock,
&msg.type,
sizeof(msg) - sizeof(long),
0);
printf("[net_sender] key event sent\n");
}
usleep(10 * 1000); // 10ms
}
return 0;
}
net_server.c
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <time.h>
#define PORT 8888
// 简单异或校验函数
static uint8_t calc_xor(uint8_t *buf, int len)
{
uint8_t c = 0;
for (int i = 0; i < len; i++)
c ^= buf[i];
return c;
}
int main(int argc, char *argv[])
{
int port = PORT;
if (argc == 2)
port = atoi(argv[1]);
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
perror("socket");
return 1;
}
struct sockaddr_in srv_addr;
memset(&srv_addr, 0, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = INADDR_ANY;
srv_addr.sin_port = htons(port);
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(listen_fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) {
perror("bind");
close(listen_fd);
return 1;
}
if (listen(listen_fd, 5) < 0) {
perror("listen");
close(listen_fd);
return 1;
}
printf("[net_server] listening on port %d...\n", port);
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
int conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len);
if (conn_fd < 0) {
perror("accept");
close(listen_fd);
return 1;
}
char cli_ip[32];
inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, sizeof(cli_ip));
printf("[net_server] connected by %s:%d\n", cli_ip, ntohs(cli_addr.sin_port));
uint8_t buf[16];
while (1) {
int n = recv(conn_fd, buf, sizeof(buf), 0);
if (n <= 0) {
printf("[net_server] client disconnected\n");
break;
}
if (n == 6 && buf[0] == 0x01) {
// 心跳包
uint32_t ts;
memcpy(&ts, &buf[1], 4);
uint8_t chk = buf[5];
if (chk != calc_xor(buf, 5))
printf("[net_server] heartbeat checksum error\n");
else
printf("[net_server] heartbeat received, ts=%u\n", ts);
} else if (n >= 8) {
// 按键事件
uint8_t type = buf[0];
uint8_t key_id = buf[1];
uint8_t event = buf[2];
uint32_t ts;
memcpy(&ts, &buf[3], 4);
uint8_t chk = buf[7];
if (chk != calc_xor(buf, 7))
printf("[net_server] key event checksum error\n");
else
printf("[net_server] key event: type=%02x key_id=%02x event=%02x ts=%u\n",
type, key_id, event, ts);
} else {
printf("[net_server] unknown data: ");
for (int i = 0; i < n; i++)
printf("%02x ", buf[i]);
printf("\n");
}
}
close(conn_fd);
close(listen_fd);
return 0;
}



Step 3:把真按键接进来
msg_def.h
通信协议核心约定:定义按键消息结构体(含pack(1)字节对齐)、消息类型宏、异或校验函数,保证所有程序消息格式 / 校验规则一致。
key_process_v2.c
真实硬件按键事件生产者:读取/dev/input/event1的 KEY0 按下事件,按约定封装带时间戳、校验位的结构化消息,写入 System V 消息队列。
net_sender.c
消息队列与 TCP 的桥接程序:
• 作为 IPC 消费者,非阻塞读取消息队列中的按键消息;
• 作为 TCP 客户端,连接指定服务器,每秒发送心跳包保活,同时转发按键消息到服务器。
net_server.c
TCP 服务端:监听指定端口(默认 8888),接收客户端连接,解析心跳包(校验 + 超时检测)和按键事件包(校验 + 结构化打印),是数据最终接收 / 解析端。
msg_def.h
c
#ifndef MSG_DEF_H
#define MSG_DEF_H
#include <stdint.h>
#include <time.h>
#define MSG_TYPE_KEY 2
#pragma pack(1)
/* 按键事件消息结构(System V 消息队列) */
struct key_msg {
long mtype; // 消息类型
uint8_t type; // 0x02 = 按键事件
uint8_t key_id; // 0x00 = KEY0
uint8_t event; // 0x01 = 按下
uint32_t ts; // 时间戳
uint8_t checksum; // 异或校验
};
#pragma pack()
static inline uint8_t calc_xor(uint8_t *buf, int len)
{
uint8_t c = 0;
for (int i = 0; i < len; i++)
c ^= buf[i];
return c;
}
key_process_v2.c

c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/input.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <time.h>
#include "msg_def.h"
#define KEY_DEV "/dev/input/event1"
#define TARGET_KEY_CODE 3 // KEY0,根据之前用的
int main(void)
{
key_t key = ftok("/tmp", 'k');
if (key == -1) {
perror("ftok");
return 1;
}
int msqid = msgget(key, IPC_CREAT | 0666);
if (msqid < 0) {
perror("msgget");
return 1;
}
int fd = open(KEY_DEV, O_RDONLY);
if (fd < 0) {
perror("open key device");
return 1;
}
printf("[key_process] started, msqid=%d\n", msqid);
printf("[key_process] listening on %s\n", KEY_DEV);
struct input_event ev;
while (1) {
int ret = read(fd, &ev, sizeof(ev));
if (ret != sizeof(ev))
continue;
if (ev.type == EV_KEY &&
ev.code == TARGET_KEY_CODE &&
ev.value == 1) // 只认按下
{
struct key_msg msg;
msg.mtype = MSG_TYPE_KEY;
msg.type = 0x02;
msg.key_id = 0x00; // KEY0
msg.event = 0x01; // DOWN
msg.ts = time(NULL);
msg.checksum = calc_xor(
(uint8_t *)&msg.type,
sizeof(msg) - sizeof(long) - 1
);
if (msgsnd(msqid, &msg,
sizeof(msg) - sizeof(long),
0) < 0) {
perror("msgsnd");
} else {
printf("[key_process] KEY0 DOWN → queue\n");
}
}
}
close(fd);
return 0;
}
net_sender.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <time.h>
#include "msg_def.h"
static int tcp_connect(const char *ip, int port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return -1;
}
struct sockaddr_in srv;
memset(&srv, 0, sizeof(srv));
srv.sin_family = AF_INET;
srv.sin_port = htons(port);
inet_pton(AF_INET, ip, &srv.sin_addr);
if (connect(sock, (struct sockaddr *)&srv, sizeof(srv)) < 0) {
perror("connect");
close(sock);
return -1;
}
printf("[net_sender] TCP connected to %s:%d\n", ip, port);
return sock;
}
static void send_heartbeat(int sock)
{
uint8_t buf[6];
uint32_t ts = time(NULL);
buf[0] = 0x01;
memcpy(&buf[1], &ts, 4);
buf[5] = calc_xor(buf, 5);
send(sock, buf, sizeof(buf), 0);
printf("[net_sender] heartbeat sent\n");
}
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("Usage: %s <server_ip> <port>\n", argv[0]);
return 1;
}
const char *server_ip = argv[1];
int port = atoi(argv[2]);
key_t key = ftok("/tmp", 'k');
int msqid = msgget(key, IPC_CREAT | 0666);
if (msqid < 0) {
perror("msgget");
return 1;
}
int sock = tcp_connect(server_ip, port);
if (sock < 0)
return 1;
time_t last_hb = 0;
while (1) {
time_t now = time(NULL);
/* 心跳 */
if (now - last_hb >= 1) {
send_heartbeat(sock);
last_hb = now;
}
/* 读取假按键事件 */
struct key_msg msg;
if (msgrcv(msqid, &msg,
sizeof(msg) - sizeof(long),
MSG_TYPE_KEY,
IPC_NOWAIT) > 0) {
send(sock,
&msg.type,
sizeof(msg) - sizeof(long),
0);
printf("[net_sender] key event sent\n");
}
usleep(10 * 1000); // 10ms
}
return 0;
}
net_server.c
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <time.h>
#define PORT 8888
#define HEARTBEAT_TIMEOUT 2 // 2秒超时
// 简单异或校验函数
static uint8_t calc_xor(uint8_t *buf, int len)
{
uint8_t c = 0;
for (int i = 0; i < len; i++)
c ^= buf[i];
return c;
}
int main(int argc, char *argv[])
{
int port = PORT;
if (argc == 2)
port = atoi(argv[1]);
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
perror("socket");
return 1;
}
struct sockaddr_in srv_addr;
memset(&srv_addr, 0, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = INADDR_ANY;
srv_addr.sin_port = htons(port);
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(listen_fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) {
perror("bind");
close(listen_fd);
return 1;
}
if (listen(listen_fd, 5) < 0) {
perror("listen");
close(listen_fd);
return 1;
}
printf("[net_server] listening on port %d...\n", port);
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
int conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len);
if (conn_fd < 0) {
perror("accept");
close(listen_fd);
return 1;
}
char cli_ip[32];
inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, sizeof(cli_ip));
printf("[net_server] connected by %s:%d\n", cli_ip, ntohs(cli_addr.sin_port));
uint8_t buf[16];
time_t last_heartbeat = time(NULL);
int heartbeat_timeout_printed = 0; // 是否已打印超时信息
while (1) {
// 非阻塞检查超时
time_t now = time(NULL);
if (!heartbeat_timeout_printed && (now - last_heartbeat > HEARTBEAT_TIMEOUT)) {
printf("[8888端口] 心跳超时,连接异常\n");
heartbeat_timeout_printed = 1;
}
int n = recv(conn_fd, buf, sizeof(buf), MSG_DONTWAIT); // 非阻塞
if (n < 0) {
usleep(100000); // 100ms等待,避免CPU空转
continue;
} else if (n == 0) {
printf("[net_server] client disconnected\n");
break;
}
if (n == 6 && buf[0] == 0x01) {
// 心跳包
uint32_t ts;
memcpy(&ts, &buf[1], 4);
uint8_t chk = buf[5];
if (chk != calc_xor(buf, 5))
printf("[net_server] heartbeat checksum error\n");
else {
last_heartbeat = time(NULL);
if (heartbeat_timeout_printed) {
printf("[net_server] heartbeat recovered, ts=%u\n", ts);
heartbeat_timeout_printed = 0;
} else {
printf("[net_server] heartbeat received, ts=%u\n", ts);
}
}
} else if (n >= 8) {
// 按键事件
uint8_t type = buf[0];
uint8_t key_id = buf[1];
uint8_t event = buf[2];
uint32_t ts;
memcpy(&ts, &buf[3], 4);
uint8_t chk = buf[7];
if (chk != calc_xor(buf, 7))
printf("[net_server] key event checksum error\n");
else
printf("[net_server] key event: type=%02x key_id=%02x event=%02x ts=%u\n",
type, key_id, event, ts);
} else {
printf("[net_server] unknown data: ");
for (int i = 0; i < n; i++)
printf("%02x ", buf[i]);
printf("\n");
}
}
close(conn_fd);
close(listen_fd);
return 0;
}
#endif
① 给开发板 eth1 配 IP(现在还没)
ip addr add 192.168.10.2/24 dev eth1
确认:
ip addr show eth1
你应该看到:
inet 192.168.10.2/24
② 给虚拟机 ens36 配 IP(对应它)
sudo ip addr add 192.168.10.100/24 dev ens36
确认:
ip addr show ens36

模拟故障断网
sudo ip link set ens36 down
sudo ip link set ens36 up
ip addr add 192.168.10.100/24 dev ens36

Step 4:TCP 跑稳后,再动 UDP
msg_def.h 和 key_process_v2.c跟之前一样,不用改动
net_sender_udp.c
c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <time.h>
#include <pthread.h>
#include "msg_def.h"
#define HEARTBEAT_INTERVAL 1 // 秒
typedef struct {
int sockfd;
struct sockaddr_in srv_addr;
} thread_param_t;
// 心跳线程
void *heartbeat_thread(void *arg)
{
thread_param_t *p = (thread_param_t *)arg;
uint8_t buf[6];
while (1) {
buf[0] = 0x01;
uint32_t ts = (uint32_t)time(NULL);
memcpy(&buf[1], &ts, 4);
buf[5] = calc_xor(buf, 5);
if (sendto(p->sockfd, buf, sizeof(buf), 0,
(struct sockaddr *)&p->srv_addr, sizeof(p->srv_addr)) < 0)
perror("sendto heartbeat");
else
printf("[net_sender] heartbeat sent, ts=%u\n", ts);
sleep(HEARTBEAT_INTERVAL);
}
return NULL;
}
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("Usage: %s <server_ip> <port>\n", argv[0]);
return 1;
}
const char *server_ip = argv[1];
int port = atoi(argv[2]);
key_t key = ftok("/tmp", 'k');
int msqid = msgget(key, IPC_CREAT | 0666);
if (msqid < 0) { perror("msgget"); return 1; }
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) { perror("socket"); return 1; }
struct sockaddr_in srv_addr;
memset(&srv_addr, 0, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(port);
if (inet_pton(AF_INET, server_ip, &srv_addr.sin_addr) <= 0) {
perror("inet_pton"); return 1;
}
printf("[net_sender] UDP ready to send to %s:%d\n", server_ip, port);
// 启动心跳线程
pthread_t tid;
thread_param_t param = {sockfd, srv_addr};
pthread_create(&tid, NULL, heartbeat_thread, ¶m);
// 读取消息队列按键事件
struct key_msg msg;
uint8_t buf[8];
while (1) {
if (msgrcv(msqid, &msg, sizeof(msg)-sizeof(long), MSG_TYPE_KEY, IPC_NOWAIT) > 0) {
buf[0] = msg.type;
buf[1] = msg.key_id;
buf[2] = msg.event;
memcpy(&buf[3], &msg.ts, 4);
buf[7] = calc_xor(buf, 7);
if (sendto(sockfd, buf, sizeof(buf), 0,
(struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0)
perror("sendto key event");
else
printf("[net_sender] key event sent\n");
}
usleep(10 * 1000); // 10ms
}
close(sockfd);
return 0;
}
net_server_udp.c
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <time.h>
#define PORT 8888
#define HEARTBEAT_TIMEOUT 2 // 2秒超时
static uint8_t calc_xor(uint8_t *buf, int len)
{
uint8_t c = 0;
for (int i = 0; i < len; i++)
c ^= buf[i];
return c;
}
int main(int argc, char *argv[])
{
int port = PORT;
if (argc == 2) port = atoi(argv[1]);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) { perror("socket"); return 1; }
struct sockaddr_in srv_addr;
memset(&srv_addr, 0, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = INADDR_ANY;
srv_addr.sin_port = htons(port);
if (bind(sockfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) {
perror("bind"); close(sockfd); return 1;
}
printf("[net_server] UDP listening on port %d...\n", port);
uint8_t buf[16];
time_t last_heartbeat = time(NULL);
int heartbeat_timeout_printed = 0;
while (1) {
struct sockaddr_in cli_addr;
socklen_t addrlen = sizeof(cli_addr);
int n = recvfrom(sockfd, buf, sizeof(buf), MSG_DONTWAIT,
(struct sockaddr *)&cli_addr, &addrlen);
time_t now = time(NULL);
if (!heartbeat_timeout_printed && (now - last_heartbeat > HEARTBEAT_TIMEOUT)) {
printf("[net_server] 心跳超时,连接异常\n");
heartbeat_timeout_printed = 1;
}
if (n < 0) { usleep(100*1000); continue; }
if (n == 6 && buf[0] == 0x01) {
uint32_t ts; memcpy(&ts, &buf[1], 4);
uint8_t chk = buf[5];
if (chk != calc_xor(buf, 5))
printf("[net_server] heartbeat checksum error\n");
else {
last_heartbeat = time(NULL);
if (heartbeat_timeout_printed) {
printf("[net_server] heartbeat recovered, ts=%u\n", ts);
heartbeat_timeout_printed = 0;
} else {
printf("[net_server] heartbeat received, ts=%u\n", ts);
}
}
} else if (n >= 8) {
uint8_t type = buf[0], key_id = buf[1], event = buf[2];
uint32_t ts; memcpy(&ts, &buf[3], 4);
uint8_t chk = buf[7];
if (chk != calc_xor(buf,7))
printf("[net_server] key event checksum error\n");
else
printf("[net_server] key event: type=%02x key_id=%02x event=%02x ts=%u\n",
type, key_id, event, ts);
} else {
printf("[net_server] unknown data: ");
for (int i=0;i<n;i++) printf("%02x ", buf[i]);
printf("\n");
}
}
close(sockfd);
return 0;
}
net_server_udp.c
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <time.h>
#define PORT 8888
#define HEARTBEAT_TIMEOUT 2 // 2秒超时
static uint8_t calc_xor(uint8_t *buf, int len)
{
uint8_t c = 0;
for (int i = 0; i < len; i++)
c ^= buf[i];
return c;
}
int main(int argc, char *argv[])
{
int port = PORT;
if (argc == 2) port = atoi(argv[1]);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) { perror("socket"); return 1; }
struct sockaddr_in srv_addr;
memset(&srv_addr, 0, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = INADDR_ANY;
srv_addr.sin_port = htons(port);
if (bind(sockfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) {
perror("bind"); close(sockfd); return 1;
}
printf("[net_server] UDP listening on port %d...\n", port);
uint8_t buf[16];
time_t last_heartbeat = time(NULL);
int heartbeat_timeout_printed = 0;
while (1) {
struct sockaddr_in cli_addr;
socklen_t addrlen = sizeof(cli_addr);
int n = recvfrom(sockfd, buf, sizeof(buf), MSG_DONTWAIT,
(struct sockaddr *)&cli_addr, &addrlen);
time_t now = time(NULL);
if (!heartbeat_timeout_printed && (now - last_heartbeat > HEARTBEAT_TIMEOUT)) {
printf("[net_server] 心跳超时,连接异常\n");
heartbeat_timeout_printed = 1;
}
if (n < 0) { usleep(100*1000); continue; }
if (n == 6 && buf[0] == 0x01) {
uint32_t ts; memcpy(&ts, &buf[1], 4);
uint8_t chk = buf[5];
if (chk != calc_xor(buf, 5))
printf("[net_server] heartbeat checksum error\n");
else {
last_heartbeat = time(NULL);
if (heartbeat_timeout_printed) {
printf("[net_server] heartbeat recovered, ts=%u\n", ts);
heartbeat_timeout_printed = 0;
} else {
printf("[net_server] heartbeat received, ts=%u\n", ts);
}
}
} else if (n >= 8) {
uint8_t type = buf[0], key_id = buf[1], event = buf[2];
uint32_t ts; memcpy(&ts, &buf[3], 4);
uint8_t chk = buf[7];
if (chk != calc_xor(buf,7))
printf("[net_server] key event checksum error\n");
else
printf("[net_server] key event: type=%02x key_id=%02x event=%02x ts=%u\n",
type, key_id, event, ts);
} else {
printf("[net_server] unknown data: ");
for (int i=0;i<n;i++) printf("%02x ", buf[i]);
printf("\n");
}
}
close(sockfd);
return 0;
}


