写在前面本文是我在完成「Linux 远程终端管理系统LSD」项目后的完整学习总结。该项目是一个基于 TCP 协议的 C/S 架构系统涵盖了 Linux 网络编程、多线程、链表数据结构、信号处理、文件 IO 等核心知识点。适合有一定 C 语言基础、正在学习 Linux 系统编程的同学参考。一、项目概述1.1 项目简介LSDLinux 远程终端管理系统是一个基于 TCP 协议的 C/S 架构远程终端管理系统实现了多客户端注册登录、远程命令执行、心跳检测、用户间聊天等核心功能。技术栈C 语言 · Linux Socket · pthread 多线程 · 信号处理 · 链表 · 文件 IO开发周期7 天含答辩运行环境Linux / WSL1.2 功能清单功能属性说明TCP 通信搭建必选socket → bind → listen → accept → connect多客户端支持多线程必选pthread_createwhile 循环 accept账户文件文本保存用户数据必选fprintf/fscanf 读写 account.txt注册查重必选遍历用户链表检查用户名是否已存在登录密码验证重复登录检测3次限制必选三重校验机制远程命令ls -l / pwd / ifconfig / ipcs必选popen() 执行并捕获输出链表管理用户链表在线链表互斥锁保护必选双链表 pthread_mutex_t心跳机制客户端每3秒发送必选alarm(3) SIGALRM 信号心跳异常15秒无心跳自动断开必选独立检测线程定期扫描聊天客户端间通过服务器转发必选服务器查找目标 sockfd 转发协议封装struct protocol 统一收发必选结构体打包/拆包配置文件config.ini任选sscanf 解析 keyvalue信号退出CtrlC 优雅退出任选SIGINT 捕获 通知服务器二、系统架构设计2.1 整体架构2.2 项目目录结构2.3 服务器线程模型服务器采用「主线程 accept 子线程处理 检测线程」的三线程模型主线程while(1)循环accept()每收到一个连接就pthread_create开一个子线程子线程client_thread循环read协议包switch-case分发处理心跳检测线程独立线程每 5 秒扫描在线链表超时则断开2.4 客户端线程模型客户端采用「登录前同步 登录后异步」的混合模型登录前主线程自己收发同步通信注册/登录结果直接 read登录后主线程只发不收启动独立的recv_thread负责所有读取异步通信心跳alarm(3)定时触发SIGALRM信号处理函数内发包设计思考为什么登录后要切换为异步因为登录后客户端可能随时收到聊天消息、命令输出、在线用户列表等多种类型的数据如果继续用同步方式主线程在等待用户输入时会阻塞 read导致无法及时接收服务器推送的消息。开一个接收线程就能完美解决这个问题。三、协议包设计核心3.1 协议结构体所有网络通信数据都封装在统一的结构体中3.2 数据包类型定义类型宏值说明TYPE_LOGIN0登录数据包TYPE_REG1注册数据包TYPE_MSG2聊天消息包TYPE_HEART3心跳数据包TYPE_CMD4远程命令包TYPE_ONLINE5查看在线用户TYPE_OK6成功响应TYPE_ERR7错误响应TYPE_QUIT8退出下线包3.3 为什么用结构体而不是字符串面试常考结构体通信 vs 字符串通信的优劣对比项结构体通信字符串通信解析效率直接按字段偏移读取O(1)需要分割/解析O(n)类型安全字段类型明确全是字符串需转换扩展性加字段需改结构体需重新编译双方加分隔符即可跨平台需注意字节序和对齐天然跨平台粘包处理固定大小每次 read 固定字节需自定义分隔符本项目选择结构体通信因为 C/S 双方都是自己写的 C 程序结构体更高效、更直观。四、核心模块详解4.1 TCP 通信搭建服务器端流程踩坑点htons()和ntohs()不能漏端口是 short 类型必须从主机字节序转换为网络字节序大端序否则端口对不上。客户端流程4.2 多线程支持多客户端服务器主循环中每accept到一个新连接就创建一个子线程关键点传给线程函数的参数必须是malloc分配的不能传局部变量地址因为accept循环很快局部变量在下一次循环时会被覆盖子线程可能读到错误数据。在线程函数开头free(info)释放即可。子线程通过switch-case分发协议包4.3 双链表管理 互斥锁两条链表的设计链表节点结构用途用户链表username password保存所有注册用户注册查重 登录验证在线链表username sockfd ip port last_heartbeat保存当前在线用户重复登录检测 聊天转发 心跳检测互斥锁保护两条链表各配一把锁减小锁粒度避免不必要的互斥每次访问链表时加锁、用完即解锁面试考点为什么不只用一把锁用户链表和在线链表是两个独立的资源如果只用一把锁注册操作访问用户链表会阻塞心跳更新访问在线链表造成不必要的等待两把锁各自保护各自的数据互不干扰提高并发性能链表关键操作删除节点在线链表用户下线时调用心跳时间更新收到心跳包时调用4.4 注册功能客户端录入用户名密码 → 打包TYPE_REG发送 → 服务器查重 → 返回结果服务器端处理查重 → 插入链表 → 保存文件4.5 登录功能三重校验登录功能实现了密码验证 重复登录检测 3次限制的三重校验服务器端的双重校验4.6 远程命令执行客户端发送 shell 命令服务器用popen()执行并捕获输出知识扩展popen()vssystem()vsexec()函数能否捕获输出是否阻塞安全性system()不能输出到终端是低命令注入风险popen()能通过管道读是中exec()需配合 dup2替换进程高需 fork本项目选择popen()因为既能执行命令又能捕获输出最简洁。4.7 心跳机制客户端alarm SIGALRM 定时发送关键点信号处理函数内必须再次调用alarm()否则闹钟只响一次。这就实现了「信号 alarm」的周期定时器。服务器独立线程检测超时设计思考删除链表节点时curr被释放了不能用curr curr-next野指针必须用curr prev-next。这是一个容易踩的坑。4.8 聊天功能服务器转发聊天是客户端 → 服务器 → 目标客户端的转发模式为什么需要sockfd → name和name → sockfd两个查找方向sockfd → name服务器收到消息时只知道是哪个 socket 发来的需要反查用户名才能告诉接收者谁发的name → sockfd转发消息时知道目标用户名需要找到对应的 socket 才能发过去4.9 配置文件解析通过fgets逐行读取sscanf解析keyvalue格式4.10 优雅退出信号处理客户端捕获SIGINTCtrlC先通知服务器再退出服务器收到TYPE_QUIT后从在线链表删除该用户五、Makefile 构建项目使用 Makefile 实现模块化编译服务器和客户端分别编译注意必须加-pthread参数否则链接时找不到pthread_create等函数。六、关键技术点总结6.1 知识图谱知识领域涉及的 API / 技术网络编程socket, bind, listen, accept, connect, read, write, close字节序转换htons, ntohs, inet_addr, inet_ntop多线程pthread_create, pthread_detach, pthread_mutex_t信号处理signal, alarm, SIGALRM, SIGINT, SIGPIPE, SIG_IGN文件 IOfopen, fclose, fprintf, fscanf, fgets, popen, pclose链表带头节点的单链表插入/删除/查找/释放配置解析fgets sscanf 解析 keyvalue错误处理perror, errno, ERR_EXIT 宏6.2 踩过的坑与经验坑 1线程参数传递坑 2SIGPIPE 信号导致服务器崩溃当客户端突然断开服务器向已关闭的 socketwrite时会触发SIGPIPE默认行为是终止进程。解决方案坑 3scanf 吃换行符scanf(%d, choice)后输入缓冲区残留\n后续fgets/getchar会读到空行。解决方案坑 4链表删除节点后不能继续用 curr-next坑 5setsockopt 地址复用服务器重启时上次绑定的端口处于TIME_WAIT状态导致bind失败。解决坑 6心跳闹钟只响一次alarm()是一次性的信号处理函数内必须再次调用七、运行效果7.1 编译运行7.2 使用流程7.3 服务器端输出示例八、项目反思与改进方向8.1 当前不足密码明文存储account.txt 中密码是明文实际应用需加密如 MD5/SHA单进程瓶颈多线程模型在大量并发时线程开销大可考虑线程池或 epoll无日志系统目前只 printf 到终端未持久化日志文件命令注入风险popen()直接执行用户输入的命令实际应用需白名单过滤8.2 可扩展方向密码加密存储MD5/SHA256引入 epoll 替代多线程提升并发性能添加日志文件记录连接/命令/断开/心跳超时命令白名单过滤只允许 ls/pwd/ifconfig 等安全命令群聊功能文件传输功能九、总结这个项目虽然不大但麻雀虽小五脏俱全把 Linux C 编程的核心知识点都串了起来知识点在项目中的应用TCP Socket 编程服务器和客户端的通信基础多线程编程服务器并发处理多客户端互斥锁保护全局链表的线程安全信号处理心跳定时器 优雅退出链表数据结构用户管理和在线管理文件 IO账户持久化和配置文件读取结构体协议统一的通信数据格式Makefile模块化编译最大的收获通过在上海职坐标学习编写这个项目真正理解了 C/S 架构的设计思路掌握了 Linux 系统编程的核心 API也踩了不少多线程和信号处理的坑。纸上得来终觉浅绝知此事要躬行。