秒懂Linux之Socket编程(四)
目录
代码前身
多线程远程命令执行
命令处理
疑难杂症
基本框架
添加指令判断函数
最终命令处理
Main主函数
safe.txt
实现效果
全部代码
代码前身
verson2:多线程&&线程池 · 516aa13 · 玛丽亚后/keep - Gitee.com
多线程远程命令执行
基本流程:由客户端发送命令,然后服务端接收并执行命令~
当前我们是把处理命令的业务交给回调函数去执行,服务端只需要读取命令即可~
命令处理
接下来我们来对命令方面的处理进行封装~
疑难杂症
但在学习前我将提出几点疑问~
按照我的逻辑客户端发送命令,服务端接收并读取命令后将命令重定向到输入端相当于执行命令,这样就够了吧~
但真正的情况是有两种方法:
第一种:1.fork&&pipe 2.dup(管道重定向)3.exec(执行外部命令函数) 3.dup
其一:为什么创建子进程,万一该命令是杀死进程那服务端岂不是被中断服务了,所以创建子进程可以保证服务端不会因为其他异常而退出,因为父子进程之间是独立的。
其二:为什么要创建管道,通过管道,父进程可以捕获子进程的标准输出(stdout),从而获取外部命令的执行结果。父进程读取命令,创建子进程去执行命令,子进程执行结果通过管道返回给父进程,父进程拿到结果再返回给客户端~
但这种方法有一个问题:命令本身就是要创建进程,而我们的背景又是多线程~这样就会出现线程去创建进程的结果,不太推荐~
第二种:popen:底层自动建立管道与子进程
popen
会返回一个FILE *
类型的指针,这个指针可以用于读取子进程的输出(如果模式为"r"
)或向子进程发送输入(如果模式为"w"
)。
使用popen函数只需要知道它默认让父进程去读取子进程执行的结果,我们只要确保父进程成功接收命令即可,其他事情不用我们担心,它会内部实现,我们等待结果就好了~
基本框架
//以空格作分隔符
const std::string sep = " ";
class Command
{
public:
Command()
{}
//对命令的处理
std::string Excute(const std::string &cmd) // ls -a -l
{
//popen :由底层自动去建立管道与子进程 再通过file* 去读取cmd执行结果
FILE* fp = popen(cmd.c_str(),"r");
if(fp==nullptr)
{
return "failed";
}
std::string result;
//作为读取命令的缓冲区
char buffer[1024];
while(fgets(buffer,sizeof(buffer),fp)!=NULL)
{
//把最终命令读取并记录到result中
result+=buffer;
}
pclose(fp);
return result;
}
~Command()
{}
private:
};
客户端启动参考
int main(int argc, char *argv[])
{
Command cmd;
std::string res = cmd.Excute("ls -a -l");
std::cout<<res;
return 0;
}
接下来我们要把一些安全指令加载进Command中,只有识别到是安全命令才会去执行
private:
//加载安全指令到Command中
void LoadConf(const std::string &conf)
{
//打开路径下的安全指令配置文件
std::ifstream in(conf);
if (!in.is_open())
{
LOG(FATAL, "open %s error\n", conf.c_str());
return;
}
std::string line;
while (std::getline(in, line))
{
LOG(DEBUG, "load command [%s] success\n", line.c_str());
_safe_cmd.insert(line);
}
in.close();
}
添加指令判断函数
//安全指令判断
bool SafeCheck(const std::string & cmd)
{
//验证当前指令是否为已收录的安全指令
auto iter = _safe_cmd.find(cmd);
if(iter==_safe_cmd.end())
{
return false;
}
return true;
}
最终命令处理
// 对命令的处理
std::string Excute(const std::string &cmd) // ls -a -l
{
// 检查是否为安全指令
std::string result;
if (SafeCheck(cmd))
{
// popen :由底层自动去建立管道与子进程 再通过file* 去读取cmd执行结果
//父进程接收指令,子进程执行
FILE *fp = popen(cmd.c_str(), "r");
if (fp == nullptr)
{
return "failed";
}
std::string result;
// 作为读取指令结果的缓冲区
char buffer[1024];
//按行读取
while (fgets(buffer, sizeof(buffer), fp) != NULL)
{
// 把最终命令读取并记录到result中
result += buffer;
}
pclose(fp);
return result;
}
else
{
result = "非法指令\n";
}
return result;
}
Main主函数
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;
}
// ./tcpserver port
int main(int argc, char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
return 1;
}
EnableScreen();
uint16_t port = std::stoi(argv[1]);
//初始化配置文件路径
Command cmd("./safe.txt");
//绑定命令处理接口,服务端只要接收到客户端的指令立马通过调用函数去调用该接口
func_t cmdExec = std::bind(&Command::Excute, &cmd, std::placeholders::_1);
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, cmdExec);
tsvr->InitServer();
tsvr->Loop();
return 0;
}
safe.txt
ls -a -l
pwd
tree
whoami
who
uname
cat
touch
实现效果
不过也有太死板的地方,我们再来改进一下~
std::string PrefixCommand(const std::string &cmd)
{
if (cmd.empty())
return std::string();
auto pos = cmd.find(sep);
if (pos == std::string::npos)
return cmd;
else
return cmd.substr(0, pos);
}
// 安全指令判断
bool SafeCheck(const std::string &cmd)
{
std::string prefix = PrefixCommand(cmd);
// 验证当前指令是否为已收录的安全指令
auto iter = _safe_cmd.find(prefix);
if (iter == _safe_cmd.end())
{
return false;
}
return true;
}
全部代码
远程命令服务 · dfdd1d1 · 玛丽亚后/keep - Gitee.com