目錄
?
前言:
一、前文補充
二、服務端的修改
三、Command類的新增
?
前言:
好久不見,最近忙于其他事情,就耽誤了咱們的Linux的網絡部分的學習。
今天咱們先來給之前所學的TCP的部分進行一個首尾工作,主要是給大家介紹一些函數與補充一下知識點。
那么今天我們將要實現的這個將會是什么功能呢?我們預期的就是大家遠程通過客戶端連接上服務端后,可以在服務端輸入一些命令,讓我們的服務端進行執行。
一、前文補充
前面我們已經通過多線程,多進程,線程池的方式分別實現了一個我們的TCP的EchoServer,今天我們先借著之前的代碼來繼續學習。
我們之前在進行TCP的數據的讀取寫入的時候,用到的函數是大家之前見過的write與read函數。其實我們這里之所以用到他們,主要是為了幫助大家理解我們通過accept返回的文件描述符。
但實際上我們的還可以使用另外一套接口,來進行數據的傳輸與傳入。
首先就是recv:
這個函數的第一個參數一樣是一個文件描述符,第二個參數要求我們提供一個用來接收消息的緩沖區,第三個參數是這個緩沖區的大小,第四個參數咱們先暫時不用管,直接填0就可以了。
所以我們的read函數就可以變成:
int n = ::recv(sockfd,buffer,sizeof(buffer)-1,0);
0表示默認行為,即阻塞等待消息。這里使用sizeof(buffer)-1一樣是為了手動最后添上字符串終止符?\0
與之對應的,在客戶端的寫入消息,就可以使用send:
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表達式的類型。
所以我們就先定義一個類型名為:
using handler_t=std::function<std::string (std::string)>;
隨后新增該類型的類成員變量:
TcpServer(handler_t handler ,uint16_t port = defaultport): _port(port),is_running(false),_handler(handler){}......private:handler_t _handler;//回調函數執行命令調用的接口
在外界創建的時候就傳入一個lambda,如同這樣:
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:
#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的邏輯拿過來可以嗎?
肯定是可以的。但是我們都學了這么久了,還使用我們之前的方法未免不是很好,今天給大家介紹兩個函數:
popen 函數FILE *popen(const char *command, const char *mode);
這個popen函數他是什么功能呢?
:創建一個管道,fork一個子進程,并調用shell執行指定的命令
他有兩個參數,第一個參數就是傳進去的命令字符串,第二個參數就是模式,"r"
表示從命令的?標準輸出?讀取數據,若為?"w"
?則可向命令的標準輸入寫入。
沒錯,這個功能直接把我們以前所需要的做的工作全部都做了,集成到了這一個函數里。
他會返回一個文件流指針,我們可以通過這個文件流指針讀取信息。
具體操作如下:
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這個函數,負責關閉這個管道流,并等待子進程結束。
#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;
};
注意,我們還要在服務端類中手動調用回調函數并獲取返回值:
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);}
最后我們編譯運行:
?
?