从链表到多线程:基于TCP的远程终端管理系统完整实践实录

一、项目概述:

本项目是一个基于TCP 协议的多线程远程终端管理系统,使用纯C 语言实现。系统支持多客户端并发连接、用户认证、命令执行、心跳保活与状态监控等核心功能。项目旨在深入理解TCP 网络编程、多线程并发模型、自定义二进制协议设计以及内存管理等底层技术。
项目源码结构:client/ 和 server/ 分离,共用 common/ 中的协议定义与网络工具函数。
完整的代码已托管在  GitHub:https://github.com/PangXuzheng-123/RemoteTerminalSystem

二、技术架构与关键设计

1.网络通信层:自定义二进制协议与粘包处理

TCP 是面向字节流的传输协议,应用层必须自行定义消息边界。本项目采用固定长度包头 + 变长包体的二进制协议格式:
本项目使用固定结构体作为消息载体(AuthData 用于控制类消息,RecvMsgData 用于数据类消息),接收端通过循环 recv 确保收满整个结构体后再处理,从而解决粘包/拆包问题。
这一设计有效解决了TCP 粘包/拆包问题,确保每条消息的完整性和可解析性。接收函数在检测到连接异常(recv返回值≤0)时会立即返回错误,避免对不完整数据进行错误处理。

typedef struct {
    int type;                          // 消息类型(上面定义的 TYPE_*)
    char phone[PHONE_LENGTH];          // 手机号(用户的唯一标识)
    char username[NAME_LENGTH];        // 用户名(注册时用,私聊时显示)
    char id_card[USER_ID_CARD_LENGTH]; // 身份证号(仅注册时使用)
    char passwd[PASSWORD_LENGTH];      // 密码
    char shell_rec[256];               // 返回提示信息 或 命令内容
    int retval;                        // 返回值:0=成功,1=失败
    char target_phone[PHONE_LENGTH];   // 目标手机号(私聊时使用)
    time_t last_heartbeat;             // 上次心跳时间(可选)
} AuthData;
typedef struct {
    int type;                          // 消息类型
    char chat_name_send[NAME_LENGTH];  // 发送者用户名(显示用)
    char phone[PHONE_LENGTH];          // 发送者手机号(内部标识)
    char chat_data[1024];              // 聊天内容 / 在线列表 / 命令输出
    char chat_time[20];                // 时间字符串 "HH:MM:SS"
    char shell_rec[256];               // 命令执行结果(TYPE_CMD 专用)
    int chat_mode;                     // 0=群聊, 1=私聊
    char target_phone[PHONE_LENGTH];   // 目标手机号
} RecvMsgData;

图片1

2.并发模型:主从线程 + 心跳监控

·主线程:负责监听端口、接受新连接,并将客户端信息加入链表管理;
·工作线程池:每个客户端连接由独立工作线程处理请求,执行命令解析与响应;
·心跳线程:定期遍历在线客户端列表,统计连续未响应次数,达到阈值后主动断开连接并释放资源。
心跳机制采用连续未响应计数而非系统时间戳,避免了系统时钟跳变可能引发的误断连问题,提高了连接状态判断的可靠性。

图片2

3.数据结构:带哨兵头节点的单向链表

系统使用带哨兵头节点的单向链表管理在线客户端信息(fd、IP、端口、心跳计数等)。为统一插入、删除、遍历等操作逻辑,引入哨兵头节点:
·头节点不存储有效数据,仅作为链表的固定起点;
·所有操作(包括在空链表中插入或删除唯一节点)均无需特殊判空处理,代码逻辑更简洁、更不易出错。

图片3

图片4

4.日志与行为解耦:回调函数设计

网络发送函数send_with_log内置了日志记录能力,但日志输出行为通过函数指针回调实现注入:
·服务端环境传入log_event,日志写入审计文件;
·客户端或调试环境传入console_logger,日志输出至控制台;
·测试环境可传入null_logger,静默运行。
这种设计使核心发送逻辑与具体日志实现解耦,同一个函数可在不同场景下复用,符合开闭原则。

图片5

图片6

图片7

图片8

三、安全与输入校验

1. 身份证号校验(GB11643-1999)

手动实现中国公民身份证号码校验算法:
·前17位必须为数字;
·第18位校验码与加权和模11结果匹配;
·不依赖第三方库,纯C 实现,确保身份标识的合法性。

图片9

2. 终端密码输入与强度校验

·模拟生产环境无回显输入,支持退格删除,限制输入长度防止缓冲区溢出;
·输入过滤:仅允许可打印ASCII字符(32~126),拒绝控制字符、空字节 及多字节字符,避免日志截断或注入风险;
·密码强度强制要求:长度6~20,必须包含大小写字母和数字,拒绝非ASCII 字符。
密码输入(hide_password,无回显+退格支持)

图片10

密码强度校验(check_password)

图片11

3. 命令白名单机制

系统仅允许执行预定义的安全命令(如start、stop、restart、status),并对参数进行拆分校验,防止命令注入攻击。

四、资源管理与健壮性

·程序退出时完整遍历链表,释放所有节点内存,并关闭所有已打开的文件 描述符;
·所有动态内存分配均检查返回值,避免空指针解引用;
·互斥锁保护共享链表操作,防止多线程竞态条件。

图片12

五、总结与收获

通过本次项目实践,我系统性地掌握了以下核心能力:
·TCP 协议编程中粘包/拆包问题的原理与解决方案;
·多线程环境下共享资源的安全访问与同步机制;
·自定义二进制协议的设计与解析方法;
·链表、回调函数、输入校验等基础技术在真实项目中的综合运用;
·从输入到处理再到日志的全链路安全防护意识。

本项目让我深刻体会到,底层网络编程的严谨性体现在对每一个字节、每一个状态、每一次资源操作的精确控制上。后续我将继续优化协议扩展性、完善异常恢复机制,并探索非阻塞I/O与事件驱动模型,进一步提升系统的并发处理能力。
在项目开发过程中,我借助AI辅助调试代码,显著提升了开发效率。但也深刻体会到,AI并非万能——例如在调试消息监听线程时,AI建议对recv加互斥锁,导致客户端频繁被强制退出。反复排查无果后,最终通过人工逐行审查才发现问题根源:监听线程中recv本身是阻塞调用,加锁后阻塞期间一直持锁,导致心跳线程无法正常发送心跳包,服务端判定超时后踢除客户端。删除该锁后问题解决。这让我认识到,AI是高效的辅助工具,但真正的代码理解与问题定位能力,仍需依赖扎实的底层功力和系统化的排查思维。
同时也感谢在职坐标学习期间,各位老师和同学的指导与帮助,多次讨论与代码审查让我受益匪浅。

posted @ 2026-07-16 17:04  Huzhen  阅读(13)  评论(4)    收藏  举报