【Linux网络编程试验方案】

Linux网络编程试验方案

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, &param);

    // 读取消息队列按键事件
    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;
}
相关推荐
枷锁—sha2 小时前
【CTFshow-pwn系列】03_栈溢出【pwn 047】详解:Ret2Libc 之 已知关键地址
网络·安全·网络安全
袁袁袁袁满2 小时前
Linux/Window如何查网络连接/端口/套接字信息?
linux·运维·服务器·网络安全·网络连接
invicinble2 小时前
对于linux形成整体性的认识
linux·运维·arm开发
『往事』&白驹过隙;2 小时前
系统编程的内存零拷贝(Zero-Copy)技术
linux·c语言·网络·c++·物联网·iot
盐焗西兰花2 小时前
鸿蒙学习实战之路-STG系列(3/11)-用户授权管理详解
服务器·学习·harmonyos
xiaoliuliu123452 小时前
Kylin V10 安装 zlib-devel-1.2.11-20.ky10.x86_64详细步骤
linux·运维·服务器
@––––––2 小时前
力扣hot100—系列6-栈
linux·python·leetcode
XiaoHu02072 小时前
Linux高级IO
网络