news 2026/7/7 1:01:23

【知识总结】命令行系统服务端实现原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【知识总结】命令行系统服务端实现原理

命令行系统总框架

命令行系统由客户端-服务器架构由命令行客户端以及命令行服务器组成。

  • 客户端功能:
    以可执行文件的形式呈现,用户直接调用,接受用户在屏幕中的键入,并发送用户的命令行信息给服务端,
    最后接受服务端的回显,并打印。
  • 服务端功能
    首先注册命令行,接受客户端的命令行信息并解析处理,最后给客户端发送回显信息。
  • 客户端与服务器端的通讯方式
    使用AF_UNIX套接字在同一主机上通信,使用文件系统路径作为地址,进程通过路径名进行连接。
  • 其流程如下图
UserClientServerSocketFile用户键入命令行键入命令行信息并发送打开绑定文件 (AF_UNIX)确认连接发送数据 (通过 Socket)处理数据返回处理结果打印服务器返回的结果服务器连接失败错误 (文件不存在)提示错误alt[链接成功][连接失败]UserClientServerSocketFile

命令行系统服务端处理流程

全局变量与命令行注册

首先要存储所有支持的命令及其对应的处理函数,使用一个全局变量来存储这些信息。

| | | | --- | --- | | 1 | typedef void (*CommandHandler)(int client_sock, char* arg); | | 2 | | | 3 | typedef struct { | | 4 | char command[MAX_CMD_LEN]; // 命令名称 | | 5 | CommandHandler handler; // 命令处理函数 | | 6 | } CommandEntry; | | 7 | | | 8 | CommandEntry command_table[MAX_COMMANDS]; // 全局命令表 |

需要提供一个函数来注册命令以及绑定对应的处理函数
命令刚注册函数command_register 。
使用全局变量command_count记录当前注册的命令数量,避免数组越界。

| | | | --- | --- | | 1 | int command_count = 0; // 当前注册的命令数量 | | 2 | void register_command(const char* command, CommandHandler handler) { | | 3 | if (command_count < MAX_COMMANDS) { | | 4 | strncpy(command_table[command_count].command, command, MAX_CMD_LEN); | | 5 | command_table[command_count].handler = handler; | | 6 | command_count++; | | 7 | } else { | | 8 | fprintf(stderr, "Command table is full!\n"); | | 9 | } | | 10 | } |

服务器启动与连接

  1. 创建并绑定AF_UNIX套接字

    | | | | --- | --- | | 1 | int server_socket = socket(AF_UNIX, SOCK_STREAM, 0); | | 2 | struct sockaddr_un server_addr; | | 3 | memset(&server_addr, 0, sizeof(server_addr)); | | 4 | server_addr.sun_family = AF_UNIX; | | 5 | strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1); | | 6 | unlink(SOCKET_PATH); // 删除旧的套接字文件 | | 7 | if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { | | 8 | perror("Failed to bind socket"); | | 9 | close(server_socket); | | 10 | exit(EXIT_FAILURE); | | 11 | } | | 12 | listen(server_socket, 5); |
  2. 创建Epoll实例并将监听套接字添加到Epoll实例

    | | | | --- | --- | | 1 | int epfd = epoll_create1(0); | | 2 | if (epfd == -1) { | | 3 | perror("epoll_create1"); | | 4 | exit(EXIT_FAILURE); | | 5 | } | | 6 | struct epoll_event ev; | | 7 | ev.events = EPOLLIN; | | 8 | ev.data.fd = server_socket; | | 9 | epoll_ctl(epfd, EPOLL_CTL_ADD, server_socket, &ev); |
  3. 监听事件并处理
    epoll模型一般会添加新连接,从而监控这个新连接的套接字,但是在这个命令行系统中,客户端只发送一次请求,服务器回复一次后关闭连接,
    在accept后直接读取数据并处理之后,发送命令行回显,然后关闭连接。

    | | | | --- | --- | | 1 | while (1) { | | 2 | struct epoll_event events[1024]; | | 3 | int nready = epoll_wait(epfd, events, 1024, -1); | | 4 | for (int i = 0; i < nready; i++) { | | 5 | int fd = events[i].data.fd; | | 6 | if (fd == server_socket) { // 新客户端连接 | | 7 | int client_socket = accept(server_socket, NULL, NULL); | | 8 | process_request(fd); | | 9 | // 关闭 | | 10 | close(client_socket); | | 11 | } | | 12 | } | | 13 | } | | 14 | | | 15 | // 将一个已经打开的文件描述符与一个文件流关联起来,从而可以使用标准I/O库函数(如 fprintf、fscanf、fgets 等)来操作文件。 | | 16 | void process_request(int client_socket) { | | 17 | FILE *client_stream = fdopen(client_socket, "a+"); | | 18 | if (!client_stream) { | | 19 | perror("fdopen failed"); | | 20 | return; | | 21 | } | | 22 | | | 23 | char buffer[MAX_COMMAND_LENGTH]; | | 24 | if (fgets(buffer, sizeof(buffer), client_stream) == NULL) { | | 25 | fprintf(stderr, "Failed to read from client\n"); | | 26 | fclose(client_stream); | | 27 | return; | | 28 | } | | 29 | | | 30 | // 查找并执行命令 | | 31 | for (int j = 0; j < command_count; j++) { | | 32 | if (strncmp(buffer, command_table[j].command, strlen(command_table[j].command)) == 0) { | | 33 | command_table[j].handler(events[i].data.fd,buffer); | | 34 | break; | | 35 | } | | 36 | } | | 37 | | | 38 | fclose(client_stream); | | 39 | } |

处理与发送打印信息

需要定义一个结构体 CommandContext,用于存储命令的上下文信息,put_format 函数用于格式化响应字符串并保存到 CommandContext 结构体中。

| | | | --- | --- | | 1 | typedef struct CommandContext { | | 2 | char *response; | | 3 | size_t length; | | 4 | size_t allocated; | | 5 | } CommandContext; | | 6 | | | 7 | void put_format(CommandContext *ctx, const char *fmt, ...) { | | 8 | va_list args; | | 9 | va_start(args, fmt); | | 10 | vsnprintf(ctx->response, sizeof(ctx->response), fmt, args); | | 11 | va_end(args); | | 12 | } |

write_str_to_socket函数用于将响应字符串通过套接字发送给客户端。

| | | | --- | --- | | 1 | void write_str_to_socket(CommandContext *ctx, int client_socket) { | | 2 | write(client_socket, ctx->response, strlen(ctx->response)); | | 3 | } |

提前注册好的处理函数,简单发送一个hello信息,首先在处理函数中初始化上下文,然后简要打印的回显通过put_format保存在回显上下文中,最后通过write_str_to_socket发送信息

| | | | --- | --- | | 1 | void handle_echo(int client_sock, char* arg) { | | 2 | CommandContext ctx = {0}; | | 3 | put_format(ctx,"hello!!") | | 4 | write_str_to_socket(&ctx, client_socket); | | 5 | } |

流程图

全局处理函数注册表 命令行处理函数 命令行处理函数注册 创建Epoll实例并绑定AF_UNIX套接字 Epoll等待事件 接受客户端连接 解析客户端命令行数据 从注册表找到对应函数处理 初始化打印上下文 存储回显信息到上下文 发送回显信息到客户端 关闭文件流和文件描述符
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/7 0:45:25

5步完成老Mac系统升级:OpenCore Legacy Patcher终极指南

5步完成老Mac系统升级&#xff1a;OpenCore Legacy Patcher终极指南 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 还在为你的老旧Mac无法升级到最新macOS而…

作者头像 李华
网站建设 2026/7/7 0:40:13

SecHex-Spoofy深度解析:专业级HWID修改工具实战指南

SecHex-Spoofy深度解析&#xff1a;专业级HWID修改工具实战指南 【免费下载链接】SecHex-Spoofy C# HWID Changer &#x1f511;︎ Disk, Guid, Mac, Gpu, Pc-Name, Win-ID, EFI, SMBIOS Spoofing [Usermode] 项目地址: https://gitcode.com/gh_mirrors/se/SecHex-Spoofy …

作者头像 李华
网站建设 2026/7/7 0:39:31

CSS 选择器性能:别让样式匹配变成隐形税

CSS 选择器性能&#xff1a;别让样式匹配变成隐形税 一、选择器问题不会总是出现在小 Demo 里 CSS 选择器性能在小项目里很难显眼。页面节点少&#xff0c;怎么写都差不多。到了复杂后台、低代码页面或长列表&#xff0c;过深选择器、通配符、后代选择器叠起来&#xff0c;样式…

作者头像 李华
网站建设 2026/7/7 0:27:10

每日 AI 研究简报 · 2026-07-06

&#xff08;本文借助 AI 大模型及工具辅助整理&#xff09; 一句话总结&#xff1a;今日 AI 主线在「能力」与「可控」之间加速校准——持久状态攻击面、多智能体红队工具走热&#xff0c;Anthropic 模型解禁与阿里把 Agent 成本压降 99% 同步发生&#xff0c;企业开始用更便…

作者头像 李华
网站建设 2026/7/7 0:17:00

R语言工程化实践:5条让代码可维护、可协作、可交付的硬核准则

1. 项目概述&#xff1a;这不是“R语言速成课”&#xff0c;而是一份老手写给自己的备忘录我用R写了十年代码&#xff0c;从统计系研究生跑第一个lm()模型&#xff0c;到带团队维护上万行Shiny生产应用&#xff0c;中间删过37次重写的for循环&#xff0c;修过217个因stringsAsF…

作者头像 李华
网站建设 2026/7/7 0:07:43

RAG+知识图谱混合检索与Graph RAG核心对比

做企业RAG落地的团队&#xff0c;往往容易卡在一容易踩坑的选型难题&#xff1a; 当需求单纯靠向量RAG搞不定、单纯靠知识图谱也搞不定&#xff0c;必须同时依赖「文本语义理解 实体关系推理」时&#xff0c;到底是做「向量图谱混合检索」就够了&#xff0c;还是必须上「Grap…

作者头像 李华