当前位置: 首页 > news >正文

Linux网络编程:TCP的远程多线程命令执行

一、前文补充

前面我们已经通过多线程,多进程,线程池的方式分别实现了一个我们的TCP的EchoServer,今天我们先借着之前的代码来继续学习。

我们之前在进行TCP的数据的读取写入的时候,用到的函数是大家之前见过的write与read函数。其实我们这里之所以用到他们,主要是为了帮助大家理解我们通过accept返回的文件描述符。

但实际上我们的还可以使用另外一套接口,来进行数据的传输与传入。

首先就是recv:

这个函数的第一个参数一样是一个文件描述符,第二个参数要求我们提供一个用来接收消息的缓冲区,第三个参数是这个缓冲区的大小,第四个参数咱们先暂时不用管,直接填0就可以了。

所以我们的read函数就可以变成:

代码语言:javascript

AI代码解释

int n = ::recv(sockfd,buffer,sizeof(buffer)-1,0);

0表示默认行为,即阻塞等待消息。这里使用sizeof(buffer)-1一样是为了手动最后添上字符串终止符\0

与之对应的,在客户端的写入消息,就可以使用send:

代码语言:javascript

AI代码解释

int n = ::send(_sockfd, message.c_str(), message.size(), 0);

值得注意的是,不管是我们在这里使用read write还是send recv。其都是一个读取/写入不完善的操作。

为什么这样说呢?

可能要到下节序列化我才能详细给大家说明。

但是这个不完善是因为TCP的特点。还记得吗,TCP是面向字节流,UDP是面向数据报。

对于我们的UDP来说,每次传输数据都是把所有数据传输过去,而面向字节流不同。

假如我们今天要给你发送一个 hello world,那么我们一定会完整接受到hello world吗?

这是不一定的,说不定我们只会先接受到hello。更详细的内容我们会在后面进行讲解。

那么下面开始我们的今天的正题,如何给我们的服务端添加上执行命令的这些功能。


二、服务端的修改

首先,我们需要明确。在我们的服务端,仍然是通过之前写的一个回调函数HandlerRequest来让每一个线程执行。

我们想要降低耦合性,让这个执行命令的功能不于我们的服务端文件杂糅在一起,所以我们可以先另起一个头文件。通过之前的方式,创建一个执行命令的对象,然后在服务端类初始化时,通过lambda表达式传进来一个回调函数。

所以我们需要在服务端的类成员变量中新增一个变量用来回调。

那我们先规定传进来的lambda表达式的类型。

所以我们就先定义一个类型名为:

代码语言:javascript

AI代码解释

using handler_t=std::function<std::string (std::string)>;

随后新增该类型的类成员变量:

代码语言:javascript

AI代码解释

TcpServer(handler_t handler ,uint16_t port = defaultport) : _port(port), is_running(false), _handler(handler) { } ...... private: handler_t _handler;//回调函数执行命令调用的接口

在外界创建的时候就传入一个lambda,如同这样:

代码语言:javascript

AI代码解释

int main() { Command cmd; std::unique_ptr<TcpServer> tcp_ptr=std::make_unique<TcpServer>([&cmd](std::string cmdstr){ return cmd.Execute(cmdstr); }); tcp_ptr->InitServer(); tcp_ptr->Start(); return 0; }

这个方法之前我们已经使用过很多次了。所以这里就加快速度。

那么要继续实现的就是我们的这个Command类的成员方法了,如何实现呢?

三、Command类的新增

现在我们开始实现一下我们的Command类:

首先就是类成员变量,我们可以设置一个白名单或者黑名单,就是限制一下那些命令我们可以使用,哪些命令我们不能使用。

我们这里就使用白名单的思维,在成员变量中实用set,只要在我们的set里,就是可以使用的。

随后,在我们的构造函数中,添加一下可以使用的命令集,并增加一个判断是否在我们的白名单的bool函数SafeCheck:

代码语言:javascript

AI代码解释

#pragma once #include<string> #include <set> class Command { public: Command() { _white_list.insert("ls"); _white_list.insert("pwd"); _white_list.insert("ls -l"); _white_list.insert("ll"); _white_list.insert("touch"); _white_list.insert("who"); _white_list.insert("whoami"); } bool SafeCheck(const std::string &cmdstr) { auto iter = _white_list.find(cmdstr); return iter == _white_list.end() ? false : true; } std::string Execute(std::string cmdstr) { } private: std::set<std::string> _white_list; };

这样我们只需要在实现一下我们的执行命令的函数。

那么我们怎么执行呢?

我们之前是不是写过SHell,把我们之前写SHell的逻辑拿过来可以吗?

肯定是可以的。但是我们都学了这么久了,还使用我们之前的方法未免不是很好,今天给大家介绍两个函数:

代码语言:javascript

AI代码解释

popen 函数 FILE *popen(const char *command, const char *mode);

这个popen函数他是什么功能呢?

:创建一个管道,fork一个子进程,并调用shell执行指定的命令

他有两个参数,第一个参数就是传进去的命令字符串,第二个参数就是模式,"r"表示从命令的 标准输出 读取数据,若为"w"则可向命令的标准输入写入。

没错,这个功能直接把我们以前所需要的做的工作全部都做了,集成到了这一个函数里。

他会返回一个文件流指针,我们可以通过这个文件流指针读取信息。

具体操作如下:

代码语言:javascript

AI代码解释

if (!SafeCheck(cmdstr)) { return std::string(cmdstr + " 不支持"); } FILE *fp = ::popen(cmdstr.c_str(), "r"); if (nullptr == fp) { return std::string("Failed"); } char buffer[1024]; std::string result; while (true) { char *ret = ::fgets(buffer, sizeof(buffer), fp); if (!ret) break; result += ret; }

像我们输入什么whoani这种命令都是有个打印效果的,我们此时就能通过fgets来获取,并返回。

最后,与之对应的,我们会有pclose这个函数,负责关闭这个管道流,并等待子进程结束。

代码语言:javascript

AI代码解释

#pragma once #include<string> #include <set> class Command { public: Command() { _white_list.insert("ls"); _white_list.insert("pwd"); _white_list.insert("ls -l"); _white_list.insert("ll"); _white_list.insert("touch"); _white_list.insert("who"); _white_list.insert("whoami"); } bool SafeCheck(const std::string &cmdstr) { auto iter = _white_list.find(cmdstr); return iter == _white_list.end() ? false : true; } std::string Execute(std::string cmdstr) { if (!SafeCheck(cmdstr)) { return std::string(cmdstr + " 不支持"); } FILE *fp = ::popen(cmdstr.c_str(), "r"); if (nullptr == fp) { return std::string("Failed"); } char buffer[1024]; std::string result; while (true) { char *ret = ::fgets(buffer, sizeof(buffer), fp); if (!ret) break; result += ret; } pclose(fp); return result.empty() ? std::string("Done") : result; } private: std::set<std::string> _white_list; };

注意,我们还要在服务端类中手动调用回调函数并获取返回值:

代码语言:javascript

int n = ::recv(sockfd, buffer, sizeof(buffer) - 1, 0); if (n > 0) { buffer[n] = 0; // 手动置入一个结束标记 // std::string echo_str = "server echo$"; // echo_str += buffer; std::string cmd_result = _handler(buffer); ::send(sockfd, cmd_result.c_str(), cmd_result.size(), 0); }

最后我们编译运行:


http://www.jsqmd.com/news/458550/

相关文章:

  • 2026利比里亚ECTN认证优质服务机构推荐榜:办理ECTN认证、办理FORM E原产地证、办理RCEP原产地证选择指南 - 优质品牌商家
  • 简单即有效!知识图谱RAG技术进阶(非常详细),ICLR2025论文深度解读,收藏这一篇就够了!
  • 有温度的 AI 陪伴!网易小派 AI 破局 AI 玩具行业痛点,打造全新解决方案
  • Tube MPC技术突破与实战指南:构建不确定性环境下的鲁棒控制系统
  • 企业级Agent开发从入门到精通(非常详细),火山引擎AgentKit打通最后一公里,收藏这一篇就够了!
  • Python爬虫进阶:Mirage Flow智能解析动态网页与反爬对抗
  • 好用的中央空调推荐,价格和口碑哪个更重要? - 工业品牌热点
  • 4大层面解析:纽约交通数据平台的深度价值探索
  • 【MCU】【AT32】从零构建:基于离线固件包与MDK的AT32工程框架实战
  • 2026 AI原生工具链升级:DeepSeek与AI原生IDE深度联动,重塑开发效率新高度
  • AI辅助开发实战:如何用ChatGPT构建自动化赚钱系统
  • 2026年生产线铝型材优选榜单,厂家联系方式汇总,铝型材框架/4040铝型材/流水线铝型材,生产线铝型材直销厂家推荐排行 - 品牌推荐师
  • Qwen3-TTS开箱即用:无需代码,网页界面直接玩转语音克隆
  • 2026国产AI算力迭代趋势预测与DeepSeek国产化部署实践
  • 铼合金板材加工标准,高温炉隔热屏蔽专用板材 - 非研科技
  • BERT文本分割模型Docker容器化部署指南:实现环境隔离与快速迁移
  • AutoDock Vina跨平台输出文件兼容性问题深度解析与解决方案
  • 系统内存持续告急?Mem Reduct的轻量级内存优化解决方案
  • Visual C++运行时组件完全解决方案:从冲突修复到企业部署的全流程指南
  • 唐山华冶钢管口碑如何,在全球市场的性价比高吗 - myqiye
  • vLLM优化技巧:提升GLM-4-9B-Chat-1M推理速度的实用方法
  • 探讨舟山成品油资质办理老牌公司,哪家口碑比较靠谱 - 工业推荐榜
  • StructBERT本地语义分析:从安装到实战的完整教程
  • Nginx Proxy Manager中文版:零代码实现专业反向代理的终极解决方案
  • 语义分割中的金字塔池化:深入理解PSP-Net的核心思想与优化技巧
  • 拯救混乱代码!用Save Actions实现IDEA保存自动格式化的5种高阶玩法
  • Dify v0.13.2召回率突然跌至61%?紧急修复指南:ES分词器冲突、Chunking策略错配与LLM重排序器校准三重陷阱
  • 2026年金华地区高端入户门十大品牌权威发布 - 呼呼拉呼
  • C++感知模块内存泄漏难定位?用eBPF+自研trace工具链5分钟锁定对象生命周期断点
  • 铼镍合金性能特点,燃气轮机高温部件专用合金 - 非研科技