news 2026/7/29 6:36:22

C语言项目实战】基于 TCP 协议的 Linux 远程终端管理系统(LSD)—— 从零搭建 C/S 架构完整总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言项目实战】基于 TCP 协议的 Linux 远程终端管理系统(LSD)—— 从零搭建 C/S 架构完整总结

写在前面:本文是我在完成「Linux 远程终端管理系统(LSD)」项目后的完整学习总结。该项目是一个基于 TCP 协议的 C/S 架构系统,涵盖了 Linux 网络编程、多线程、链表数据结构、信号处理、文件 IO 等核心知识点。适合有一定 C 语言基础、正在学习 Linux 系统编程的同学参考。


一、项目概述

1.1 项目简介

LSD(Linux 远程终端管理系统)是一个基于 TCP 协议的 C/S 架构远程终端管理系统,实现了多客户端注册登录、远程命令执行、心跳检测、用户间聊天等核心功能。

  • 技术栈:C 语言 · Linux Socket · pthread 多线程 · 信号处理 · 链表 · 文件 IO
  • 开发周期:7 天(含答辩)
  • 运行环境:Linux / WSL

1.2 功能清单

功能

属性

说明

TCP 通信搭建

必选

socket → bind → listen → accept → connect

多客户端支持(多线程)

必选

pthread_create,while 循环 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 解析 key=value

信号退出(Ctrl+C 优雅退出)

任选

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_LOGIN

0

登录数据包

TYPE_REG

1

注册数据包

TYPE_MSG

2

聊天消息包

TYPE_HEART

3

心跳数据包

TYPE_CMD

4

远程命令包

TYPE_ONLINE

5

查看在线用户

TYPE_OK

6

成功响应

TYPE_ERR

7

错误响应

TYPE_QUIT

8

退出下线包

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 → namename → sockfd两个查找方向?

  • sockfd → name:服务器收到消息时,只知道是哪个 socket 发来的,需要反查用户名才能告诉接收者"谁发的"
  • name → sockfd:转发消息时,知道目标用户名,需要找到对应的 socket 才能发过去

4.9 配置文件解析

通过fgets逐行读取,sscanf解析key=value格式:

4.10 优雅退出(信号处理)

客户端捕获SIGINT(Ctrl+C),先通知服务器再退出:

服务器收到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

文件 IO

fopen, fclose, fprintf, fscanf, fgets, popen, pclose

链表

带头节点的单链表,插入/删除/查找/释放

配置解析

fgets + sscanf 解析 key=value

错误处理

perror, errno, ERR_EXIT 宏

6.2 踩过的坑与经验

坑 1:线程参数传递
坑 2:SIGPIPE 信号导致服务器崩溃

当客户端突然断开,服务器向已关闭的 socketwrite时会触发SIGPIPE,默认行为是终止进程。解决方案:

坑 3:scanf 吃换行符

scanf("%d", &choice)后输入缓冲区残留\n,后续fgets/getchar会读到空行。解决方案:

坑 4:链表删除节点后不能继续用 curr->next
坑 5:setsockopt 地址复用

服务器重启时,上次绑定的端口处于TIME_WAIT状态,导致bind失败。解决:

坑 6:心跳闹钟只响一次

alarm()是一次性的,信号处理函数内必须再次调用:


七、运行效果

7.1 编译运行

7.2 使用流程

7.3 服务器端输出示例


八、项目反思与改进方向

8.1 当前不足

  1. 密码明文存储:account.txt 中密码是明文,实际应用需加密(如 MD5/SHA)
  1. 单进程瓶颈:多线程模型在大量并发时线程开销大,可考虑线程池或 epoll
  1. 无日志系统:目前只 printf 到终端,未持久化日志文件
  1. 命令注入风险popen()直接执行用户输入的命令,实际应用需白名单过滤

8.2 可扩展方向

  • 密码加密存储(MD5/SHA256)
  • 引入 epoll 替代多线程,提升并发性能
  • 添加日志文件,记录连接/命令/断开/心跳超时
  • 命令白名单过滤,只允许 ls/pwd/ifconfig 等安全命令
  • 群聊功能
  • 文件传输功能

九、总结

这个项目虽然不大,但麻雀虽小五脏俱全,把 Linux C 编程的核心知识点都串了起来:

知识点

在项目中的应用

TCP Socket 编程

服务器和客户端的通信基础

多线程编程

服务器并发处理多客户端

互斥锁

保护全局链表的线程安全

信号处理

心跳定时器 + 优雅退出

链表数据结构

用户管理和在线管理

文件 IO

账户持久化和配置文件读取

结构体协议

统一的通信数据格式

Makefile

模块化编译

最大的收获:通过在上海职坐标学习,编写这个项目,真正理解了 C/S 架构的设计思路,掌握了 Linux 系统编程的核心 API,也踩了不少多线程和信号处理的坑。纸上得来终觉浅,绝知此事要躬行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/29 6:35:34

虚拟同步发电机(VSG)技术:原理、建模与工程实践

1. 虚拟同步发电机(VSG)技术背景与挑战电力电子变流器在新能源发电系统中扮演着核心角色,而虚拟同步发电机(Virtual Synchronous Generator, VSG)技术正在重塑我们对并网逆变器的认知。这项技术的本质是通过控制算法&a…

作者头像 李华
网站建设 2026/7/29 6:33:46

PPG心率传感器原理、实战与项目集成全解析

1. 从“感觉”到“数据”:心率传感器的价值跃迁最近在试用一款9月份刚上市的心率传感器模块,感触颇深。过去我们聊到心率,更多是“我感觉心跳有点快”或者“跑完步心砰砰跳”这种模糊的感知。但现在,一块指甲盖大小的传感器&#…

作者头像 李华
网站建设 2026/7/29 6:29:34

基于Halton序列的图像加密:原理、Matlab实现与相关性分析

1. 项目概述:当Halton序列遇上图像加密最近在整理一些图像处理的老项目,翻到了一个挺有意思的课题:用Halton序列来给图像做加密。这玩意儿乍一听有点“跨界”,毕竟Halton序列在金融建模、计算机图形学里更常见,用来做图…

作者头像 李华
网站建设 2026/7/29 6:28:23

射频巴伦电路设计:从原理到实践,解决单端与差分信号转换难题

1. 巴伦电路:射频世界的“翻译官”在射频工程师的日常工具箱里,有一个不起眼但至关重要的器件,它就是巴伦。我第一次接触巴伦是在调试一个2.4GHz的Wi-Fi模块天线接口时,信号总是差强人意,驻波比居高不下,直…

作者头像 李华