一、前文补充前面我们已经通过多线程多进程线程池的方式分别实现了一个我们的TCP的EchoServer今天我们先借着之前的代码来继续学习。我们之前在进行TCP的数据的读取写入的时候用到的函数是大家之前见过的write与read函数。其实我们这里之所以用到他们主要是为了帮助大家理解我们通过accept返回的文件描述符。但实际上我们的还可以使用另外一套接口来进行数据的传输与传入。首先就是recv这个函数的第一个参数一样是一个文件描述符第二个参数要求我们提供一个用来接收消息的缓冲区第三个参数是这个缓冲区的大小第四个参数咱们先暂时不用管直接填0就可以了。所以我们的read函数就可以变成代码语言javascriptAI代码解释int n ::recv(sockfd,buffer,sizeof(buffer)-1,0);0表示默认行为即阻塞等待消息。这里使用sizeof(buffer)-1一样是为了手动最后添上字符串终止符\0与之对应的在客户端的写入消息就可以使用send代码语言javascriptAI代码解释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表达式的类型。所以我们就先定义一个类型名为代码语言javascriptAI代码解释using handler_tstd::functionstd::string (std::string);随后新增该类型的类成员变量代码语言javascriptAI代码解释TcpServer(handler_t handler ,uint16_t port defaultport) : _port(port), is_running(false), _handler(handler) { } ...... private: handler_t _handler;//回调函数执行命令调用的接口在外界创建的时候就传入一个lambda如同这样代码语言javascriptAI代码解释int main() { Command cmd; std::unique_ptrTcpServer tcp_ptrstd::make_uniqueTcpServer([cmd](std::string cmdstr){ return cmd.Execute(cmdstr); }); tcp_ptr-InitServer(); tcp_ptr-Start(); return 0; }这个方法之前我们已经使用过很多次了。所以这里就加快速度。那么要继续实现的就是我们的这个Command类的成员方法了如何实现呢三、Command类的新增现在我们开始实现一下我们的Command类首先就是类成员变量我们可以设置一个白名单或者黑名单就是限制一下那些命令我们可以使用哪些命令我们不能使用。我们这里就使用白名单的思维在成员变量中实用set只要在我们的set里就是可以使用的。随后在我们的构造函数中添加一下可以使用的命令集并增加一个判断是否在我们的白名单的bool函数SafeCheck:代码语言javascriptAI代码解释#pragma once #includestring #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::setstd::string _white_list; };这样我们只需要在实现一下我们的执行命令的函数。那么我们怎么执行呢我们之前是不是写过SHell把我们之前写SHell的逻辑拿过来可以吗肯定是可以的。但是我们都学了这么久了还使用我们之前的方法未免不是很好今天给大家介绍两个函数代码语言javascriptAI代码解释popen 函数 FILE *popen(const char *command, const char *mode);这个popen函数他是什么功能呢创建一个管道fork一个子进程并调用shell执行指定的命令他有两个参数第一个参数就是传进去的命令字符串第二个参数就是模式r表示从命令的 标准输出 读取数据若为w则可向命令的标准输入写入。没错这个功能直接把我们以前所需要的做的工作全部都做了集成到了这一个函数里。他会返回一个文件流指针我们可以通过这个文件流指针读取信息。具体操作如下代码语言javascriptAI代码解释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这个函数负责关闭这个管道流并等待子进程结束。代码语言javascriptAI代码解释#pragma once #includestring #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::setstd::string _white_list; };注意我们还要在服务端类中手动调用回调函数并获取返回值代码语言javascriptint 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); }最后我们编译运行