📢博客主頁:https://blog.csdn.net/2301_779549673
📢博客倉庫:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢歡迎點贊 👍 收藏 ?留言 📝 如有錯誤敬請指正!
📢本文由 JohnKi 原創,首發于 CSDN🙉
📢未來很長,值得我們全力奔赴更美好的生活?
文章目錄
- 🏳??🌈一、protocol.hpp
- 1.1 Request類
- 1.1.1 基本結構
- 1.1.2 構造、析構函數
- 1.1.3 序列化函數
- 1.1.4 反序列化函數
- 1.1.5 其他函數
- 1.2、Response類
- 1.2.1 基本結構
- 1.2.2 構造析構函數
- 1.2.3 序列化函數
- 1.2.4 反序列化函數
- 1.2.5 打印結果
- 1.3 Factory類
- 1.4 報頭
- 1.4.1 添加報頭
- 1.4.2 解析報頭
- 🏳??🌈二、Service.hpp
- 2.1 方法回調
- 2.2 成員變量 + 構造
- 2.3 IOExcute
- 🏳??🌈三、NetCal.hpp
- 🏳??🌈四、TcpClient.cpp
- 🏳??🌈五、整體代碼
- 5.1 protocol.hpp
- 5.2 Service.hpp
- 5.3 Socket.hpp
- 5.4 TcpServer.cpp
- 5.5 TcpServer.hpp
- 5.6 NetCal.hpp
- 5.7 TcpClient.cpp
- 5.8 TcpClient.hpp
- 5.9 Makefile
- 👥總結
🏳??🌈一、protocol.hpp
該文件實現序列化與反序列使用到的類和相關函數(加報頭解報頭)!
1.1 Request類
1.1.1 基本結構
該類有三個成員變量,_x,_y,_oper(運算符號),序列化,反序列化,構造,析構及其他獲取成員變量與打印的函數!
namespace protocol{class Request{public:Request(){}// 序列化 將結構化轉成字符串bool Serialize(std::string& out);// 反序列化 將字符串轉成結構化bool Deserialize(const std::string& in);void Print();int X();int Y();char Oper();~Request(){}private:int _x;int _y;char _oper; // 計算符號};
}
1.1.2 構造、析構函數
為了方便后面的使用,此處實現兩個構造函數,一個無參,一個帶參函數,析構函數無需處理!
// 構造函數 - 無參
Request() {}
// 構造函數 - 有參
Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) {}
// 析構函數
~Request(){}
1.1.3 序列化函數
序列化即將結構化轉成字符串,并將字符串以輸出型參數輸出
// 序列化 將結構化轉成字符串bool Serialize(std::string* out){// 使用現成的庫,xml,json,protobuf等// 這里使用json庫Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::FastWriter writer;std::string s = writer.write(root);*out = s;return true;}
1.1.4 反序列化函數
反序列化即將字符串轉成結構化,參數傳入字符串!
// 反序列化 將字符串轉成結構化
bool Deserialize(const std::string& in) {Json::Value root;Json::Reader reader;bool res = reader.parse(in, root);_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return true;
}
1.1.5 其他函數
void Print() {std::cout << _x << std::endl;std::cout << _y << std::endl;std::cout << _oper << std::endl;
}int X() { return _x; }
int Y() { return _y; }
char Oper() { return _oper; }void SetValue(int x, int y, char oper) {_x = x;_y = y;_oper = oper;
}
1.2、Response類
1.2.1 基本結構
這個類我們需要組織發送給客戶端的響應,需要三個成員變量,_result(計算結果),_code(自定義錯誤碼),_desc(錯誤碼描述)
內部主要是 序列化(發送)
和 反序列化(接受)
函數!
class Response {
public:Response() : _result(0), _code(0), _desc("success") {}bool Serialize(std::string* out);bool Deserialize(const std::string& in);~Response() {}public:int _result;int _code; // 錯誤碼 0 success, 1 fail, 2 fatalstd::string _desc; // 錯誤碼描述
};
1.2.2 構造析構函數
構造函數直接手動初始化(結果和錯誤碼初始化為0,描述默認初始化為success)
析構函數無需處理!
Response() : _result(0), _code(0), _desc("success") {}
~Response() {}
1.2.3 序列化函數
這里和 request
類如出一轍,只需要構造相應的JSON結果,然后利用緊湊方法,構造出JSON風格的字符串就行了
bool Serialize(std::string* out) {Json::Value root;root["result"] = _result;root["code"] = _code;root["desc"] = _desc;Json::FastWriter writer;std::string s = writer.write(root);*out = s;return true;
}
1.2.4 反序列化函數
反序列化即將字符串轉成結構化,參數傳入字符串!
// 反序列化 - 將JSON字符串反序列化成成員變量
bool Deserialize(const std::string& in) {Json::Value root;Json::Reader reader;// parse 的作用是將 JSON 字符串解析成 Json::Value 對象bool res = reader.parse(in, root);_result = root["result"].asInt();_code = root["code"].asInt();_desc = root["desc"].asString();return true;
}
1.2.5 打印結果
將成員變量以字符串形式打印出來即可!
void PrintResult(){std::cout << "result: " << _result << ", code: " << _code << ", desc: " << _desc << std::endl;
}
1.3 Factory類
- 因為
Request類
和Response類
可能頻繁創建,因此我們可以設計一個工廠類,內部設計兩個創建類的靜態函數(沒有this指針,外部直接調用函數即可)!
class Factory {
public:static std::shared_ptr<Request> BuildRequestDefault() {return std::make_shared<Request>();}static std::shared_ptr<Response> BuildResponseDefault() {return std::make_shared<Response>();}
};
1.4 報頭
在實際的網絡通信中,傳的不僅僅是序列化后的字符串,還有報頭信息,此處我們也設計一下報頭信息
"len"\r\n"{json}"\r\n
– 完整的報文- len 有效荷載長度
\r\n
(第一個):區分 len 和 json 串\r\n
(第二個):暫時沒用
1.4.1 添加報頭
// 添加報頭
std::string AddHeader(const std::string& jsonstr) {int len = jsonstr.size();std::string lenstr = std::to_string(len);return lenstr + sep + jsonstr + sep;
}
1.4.2 解析報頭
注意:可能沒有一個有效信息或者有多個有效信息!
// 解析報頭
// 去掉前面的長度和分隔符與有效信息后面的分隔符
std::string ParseHeader(std::string& jsonstr) {// 分析auto pos = jsonstr.find(sep); // 在json風格字符串中找第一個分隔符if (pos == std::string::npos)return std::string(); // 找不到分隔符,返回空字符串// 獲取 lenstd::string lenstr = jsonstr.substr(0, pos); // 取出長度字符串int len = std::stoi(lenstr); // 轉成整數// 計算一個完整的報文應該是多長int totallen = lenstr.size() + len + 2 * sep.size();// 若傳進來的字符串長度小于報文總長,說明沒有一個完整的有效信息,返回空if (jsonstr.size() < totallen)return std::string();// 取出有效信息std::string validstr = jsonstr.substr(pos + sep.size(), len);// 去掉最后的分隔符jsonstr.erase(0, totallen);return validstr;
}
🏳??🌈二、Service.hpp
我們需要在這里處理響應的請求和進行響應
2.1 方法回調
因此我們需要提供一個方法回調,來處理json化的請求和響應
參數是請求類的指針,返回值是應答類的指針!
// 參數是請求類的指針,返回值是應答類的指針!
using process_t = std::function<std::shared_ptr<Response>(std::shared_ptr<Request>)>;
2.2 成員變量 + 構造
所以我們就需要添加一個方法回調的成員變量,并且在構造中賦值
2.3 IOExcute
IOExcute()
函數進行客戶端與服務端的通信,并處理發送過來的信息(調用執行方法),
1、接收消息
2、報文解析(保證獲取至少獲得一條有效信息,沒有則繼續接受消息)
3、反序列化(將字符串轉成結構化)
4、業務處理(調用構造函數傳入的回調函數)
5、序列化應答
6、添加len長度(報頭)
7、發送回去
// 處理請求并給出響應
void IOExcute(SockPtr sock, InetAddr& addr) {std::string message; // 寫在while外,存儲信息while (true) {// 1. 負責讀取ssize_t n = sock->Recv(&message);if (n == 0) {LOG(LogLevel::INFO)<< "client " << addr.AddrStr().c_str() << " disconnected";break;} else if (n < 0) {LOG(LogLevel::ERROR)<< "recv error for client: " << addr.AddrStr().c_str();break;}std::cout << "----------------------------------------" << std::endl;std::cout << "client " << addr.AddrStr().c_str()<< " send message: " << message << std::endl<< std::endl;// 2. 負責解析,提取報頭和有效荷載// 但此時我們仍然無法保證讀到的是完整報文std::string package = ParseHeader(message);if (package.empty())continue;auto req = Factory::BuildRequestDefault();std::cout << "package: " << package << std::endl;// 3. 反序列化req->Deserialize(package);// 4. 業務處理auto resp = _process(req);// 5. 序列化std::string respjson;resp->Serialize(&respjson);std::cout << "respjson: " << respjson << std::endl;// 6. 添加報頭std::string respstr = AddHeader(respjson);std::cout << "respstr: " << respstr << std::endl;// 7. 負責寫回sock->Send(respstr);}
}
因為我們是循環獲取報文,所以可能一次獲取的報文不完整,因此我們需要保證recv過來的message能夠保留
ssize_t Recv(std::string* out) override {char inbuffer[4096];ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if (n > 0) {inbuffer[n] = 0;*out += inbuffer; // 可能一次讀取不成功}return n;
}
🏳??🌈三、NetCal.hpp
這個類就是來處理計算機的具體過程的那個回調函數的類,要傳進一個請求類,返回一個響應類
他不需要成員變量,構造、析構為空即可
std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req){}
std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req) {auto rsp = Factory::BuildResponseDefault();switch (req->Oper()) {case '+':rsp->_result = req->X() + req->Y();break;case '-':rsp->_result = req->X() - req->Y();break;case '*':rsp->_result = req->X() * req->Y();break;case '/':if (req->Y() == 0) {rsp->_result = -1;rsp->_desc = "division by zero";} else {rsp->_result = req->X() / req->Y();}break;case '%':if (req->Y() == 0) {rsp->_result = -1;rsp->_desc = "mod by zero";} else {rsp->_result = req->X() % req->Y();}default:rsp->_result = -1;rsp->_desc = "unknown operator";break;}return rsp;
}
🏳??🌈四、TcpClient.cpp
該文件用戶創建TcpServer類對象,并調用執行函數運行客戶端!
通信操作主要包括以下七步:
1、序列化
2、添加長度報頭字段
3、發送數據
4、讀取應答,response
5、報文解析,提取報頭和有效載荷
6、反序列化
7、打印結果
#include "TcpServer.hpp"
#include "Service.hpp"
#include "NetCal.hpp"using namespace TcpServerModule;int main(int argc, char* argv[]){if(argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;Die(1);}uint16_t port = std::stoi(argv[1]);NetCal netcal;IOService service([&netcal](std::shared_ptr<Request> req) {return netcal.Calculator(req);});std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::bind(&IOService::IOExcute, &service, std::placeholders::_1, std::placeholders::_2), port);tsvr->Loop();return 0;
}
🏳??🌈五、整體代碼
5.1 protocol.hpp
#include <iostream>
#include <jsoncpp/json/json.h>namespace protocol{// `"len"\r\n"{json}"\r\n` -- 完整的報文static const std::string sep = "\r\n"; // 分隔符// 添加報頭std::string AddHeader(const std::string& jsonstr){int len = jsonstr.size();std::string lenstr = std::to_string(len);return lenstr + sep + jsonstr + sep;}// 解析報頭// 去掉前面的長度和分隔符與有效信息后面的分隔符std::string ParseHeader(std::string& jsonstr){// 分析auto pos = jsonstr.find(sep); // 在json風格字符串中找第一個分隔符if(pos == std::string::npos)return std::string(); // 找不到分隔符,返回空字符串// 獲取 lenstd::string lenstr = jsonstr.substr(0, pos); // 取出長度字符串int len = std::stoi(lenstr); // 轉成整數// 計算一個完整的報文應該是多長int totallen = lenstr.size() + len + 2 * sep.size();// 若傳進來的字符串長度小于報文總長,說明沒有一個完整的有效信息,返回空if(jsonstr.size() < totallen)return std::string();// 取出有效信息std::string validstr = jsonstr.substr(pos + sep.size(), len);// 去掉最后的分隔符jsonstr.erase(0, totallen);return validstr;}class Request{public:// 構造函數 - 無參Request(){}// 構造函數 - 有參Request(int x, int y, char oper): _x(x), _y(y), _oper(oper){}// 序列化 將結構化轉成字符串bool Serialize(std::string* out){// 使用現成的庫,xml,json,protobuf等// 這里使用json庫Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::FastWriter writer;std::string s = writer.write(root);*out = s;return true;}// 反序列化 將字符串轉成結構化bool Deserialize(const std::string& in){Json::Value root;Json::Reader reader;// parse 的作用是將 JSON 字符串解析成 Json::Value 對象bool res = reader.parse(in, root);_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return true;}void Print(){std::cout << _x << std::endl;std::cout << _y << std::endl;std::cout << _oper << std::endl;}int X(){ return _x;}int Y(){ return _y;}char Oper(){ return _oper;}void SetValue(int x, int y, char oper){_x = x;_y = y;_oper = oper;}~Request(){}private:int _x;int _y;char _oper; // 計算符號};class Response{public:Response():_result(0), _code(0), _desc("success"){}// 序列化 - 將成員變量結構化成JSON字符串bool Serialize(std::string* out){Json::Value root;root["result"] = _result;root["code"] = _code;root["desc"] = _desc;// 使用 FastWriter 快速生成緊湊的 JSON 字符串Json::FastWriter writer;std::string s = writer.write(root);// 將結果通過指針參數返回*out = s;return true; }// 反序列化 - 將JSON字符串反序列化成成員變量bool Deserialize(const std::string& in){Json::Value root;Json::Reader reader;// parse 的作用是將 JSON 字符串解析成 Json::Value 對象bool res = reader.parse(in, root);_result = root["result"].asInt();_code = root["code"].asInt();_desc = root["desc"].asString();return true;}void PrintResult(){std::cout << "result: " << _result << ", code: " << _code << ", desc: " << _desc << std::endl;}~Response(){}public:int _result;int _code; // 錯誤碼 0 success, 1 fail, 2 fatalstd::string _desc; // 錯誤碼描述};class Factory{public:static std::shared_ptr<Request> BuildRequestDefault(){return std::make_shared<Request>();}static std::shared_ptr<Response> BuildResponseDefault(){return std::make_shared<Response>();}};
}
5.2 Service.hpp
#pragma once#include <iostream>
#include <functional>#include "Log.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"
#include "protocol.hpp"using namespace LogModule;
using namespace SocketModule;
using namespace protocol;class IOService{// 參數是請求類的指針,返回值是應答類的指針!using process_t = std::function<std::shared_ptr<Response>(std::shared_ptr<Request>)>;public:IOService(process_t process) : _process(process){}// 處理請求并給出響應void IOExcute(SockPtr sock, InetAddr& addr){std::string message; // 寫在while外,存儲信息while(true){// 1. 負責讀取ssize_t n = sock->Recv(&message);if(n == 0){LOG(LogLevel::INFO) << "client " << addr.AddrStr().c_str() << " disconnected";break;}else if(n < 0){LOG(LogLevel::ERROR) << "recv error for client: " << addr.AddrStr().c_str();break;}std::cout << "----------------------------------------" << std::endl;std::cout << "client " << addr.AddrStr().c_str() << " send message: " << message << std::endl;// 2. 負責解析,去掉報頭和有效荷載// 但此時我們仍然無法保證讀到的是完整報文std::string package = ParseHeader(message);if(package.empty()) continue;auto req = Factory::BuildRequestDefault();std::cout << "package: " << package << std::endl;// 3. 反序列化// 此時 req 里面存儲著 x,y,operreq->Deserialize(package);std::cout << "x: " << req->X() << " y: " << req->Y() << " oper: " << req->Oper() << std::endl;// 4. 業務處理auto resp = _process(req);// 5. 序列化std::string respjson;resp->Serialize(&respjson);std::cout << "respjson: " << respjson << std::endl;// 6. 添加報頭std::string respstr = AddHeader(respjson);std::cout << "respstr: " << respstr << std::endl;// 7. 負責寫回sock->Send(respstr);}}~IOService(){}private:process_t _process;
};
5.3 Socket.hpp
#pragma once #include <iostream>
#include <cstring>
#include <memory>#include <netinet/in.h>#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"using namespace LogModule;const int gbacklog = 8;namespace SocketModule{class Socket;using SockPtr = std::shared_ptr<Socket>;class Socket{public:virtual void CreateSocketOrDie() = 0; // 創建套接字virtual void BindOrDie(uint16_t port) = 0; // 綁定套接字virtual void ListenOrDie(int backlog = gbacklog) = 0; // 監聽套接字virtual SockPtr Accepter(InetAddr* cli) = 0; // 獲取鏈接virtual bool Connector(const std::string& serverip, uint16_t serverport) = 0; // 簡歷連接virtual int Sockfd() = 0;virtual void Close() = 0;virtual ssize_t Recv(std::string* out) = 0; // 接收數據virtual ssize_t Send(const std::string& in) = 0; // 發送數據 public:// 創建監聽套接字void BuildListenSocket(uint16_t port){CreateSocketOrDie(); // 創建BindOrDie(port); // 綁定ListenOrDie(); // 監聽}// 創建客戶端套接字void BuildConnectorSocket(const std::string& serverip, uint16_t serverport){CreateSocketOrDie(); // 創建Connector(serverip, serverport); // 連接}}; class TcpSocket : public Socket{public:TcpSocket(){}TcpSocket(int sockfd) : _sockfd(sockfd){ }// 創建套接字void CreateSocketOrDie() override{_sockfd = socket(AF_INET, SOCK_STREAM, 0);if(_sockfd < 0){LOG(LogLevel::ERROR) << "create socket error";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "sockfd create success : " << _sockfd;}// 綁定套接字void BindOrDie(uint16_t port) override{// sockaddr_in 的頭文件是 #include <netinet/in.h>struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = htonl(INADDR_ANY);int n = ::bind(_sockfd, CONV(&local), sizeof(local));if(n < 0){LOG(LogLevel::ERROR) << "bind socket error";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success";} // 監聽套接字void ListenOrDie(int backlog = gbacklog) override{int n = ::listen(_sockfd, backlog);if(n < 0){LOG(LogLevel::ERROR) << "listen socket error";exit(LISTEN_ERR);}LOG(LogLevel::DEBUG) << "listen success";}// 獲取鏈接SockPtr Accepter(InetAddr* cli) override{struct sockaddr_in client;socklen_t clientlen = sizeof(client);// accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)// 返回一個新的套接字,該套接字與調用進程間接地建立了連接。int sockfd = ::accept(_sockfd, CONV(&client), &clientlen);if(sockfd < 0){LOG(LogLevel::ERROR) << "accept socket error";return nullptr;}*cli = InetAddr(client);LOG(LogLevel::DEBUG) << "get a new connection from " << cli->AddrStr().c_str() << ", sockfd : " << sockfd;return std::make_shared<TcpSocket>(sockfd);}// 建立連接bool Connector(const std::string& serverip, uint16_t serverport) override{struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET; // IPv4協議server.sin_port = htons(serverport); // 端口號// 這句話表示將字符串形式的IP地址轉換為網絡字節序的IP地址// inet_pton函數的作用是將點分十進制的IP地址轉換為網絡字節序的IP地址// 這里的AF_INET表示IPv4協議// 這里的serverip.c_str()表示IP地址的字符串形式// &server.sin_addr表示將IP地址存儲到sin_addr成員變量中::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); // IP地址int n = ::connect(_sockfd, CONV(&server), sizeof(server));if(n < 0){LOG(LogLevel::ERROR) << "connect socket error" ;return false;}LOG(LogLevel::DEBUG) << "connect success";return true;}// 獲取套接字描述符int Sockfd() override{ return _sockfd; }// 關閉套接字void Close() override{ if(_sockfd >= 0) ::close(_sockfd); }// 接收數據ssize_t Recv(std::string* out) override{char inbuffer[4096];ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if(n > 0){inbuffer[n] = 0;*out += inbuffer; // 可能一次讀取不成功}return n;} // 發送數據 ssize_t Send(const std::string& in) override{return ::send(_sockfd, in.c_str(), in.size(), 0);}~TcpSocket(){}private:int _sockfd;};}
5.4 TcpServer.cpp
#include "TcpServer.hpp"
#include "Service.hpp"
#include "NetCal.hpp"using namespace TcpServerModule;int main(int argc, char* argv[]){if(argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;Die(1);}uint16_t port = std::stoi(argv[1]);NetCal netcal;IOService service([&netcal](std::shared_ptr<Request> req) {return netcal.Calculator(req);});std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::bind(&IOService::IOExcute, &service, std::placeholders::_1, std::placeholders::_2), port);tsvr->Loop();return 0;
}
5.5 TcpServer.hpp
#pragma once#include <iostream>
#include <memory>
#include <functional>
#include <sys/wait.h>#include "Thread.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"namespace TcpServerModule{using namespace SocketModule;using namespace LogModule;using service_t = std::function<void(SocketModule::SockPtr, InetAddr&)>;class TcpServer{public:TcpServer(service_t service, uint16_t port): _port(port), _listensock(std::make_shared<TcpSocket>()),_isrunning(false), _service(service){_listensock->BuildListenSocket(port);}void Loop(){_isrunning = true;while(_isrunning){InetAddr client;// 獲取客戶端連接SockPtr cli = _listensock->Accepter(&client);if(cli == nullptr) continue;LOG(LogLevel::DEBUG) << "get a new connection from " << client.AddrStr().c_str();// 獲取成功pthread_t tid;// ThreadData 的頭文件是 ThreadData* td = new ThreadData(cli, this, client);pthread_create(&tid, nullptr, Execute, td); // 新線程分離}}// 線程函數參數對象class ThreadData{public:SockPtr _sockfd;TcpServer* _self;InetAddr _addr;public:ThreadData(SockPtr sockfd, TcpServer* self, const InetAddr& addr): _sockfd(sockfd), _self(self), _addr(addr){}};// 線程函數static void* Execute(void* args){ThreadData* td = static_cast<ThreadData*>(args);// 子線程結束后由系統自動回收資源,無需主線程調用 pthread_joinpthread_detach(pthread_self()); // 分離新線程,無需主線程回收td->_self->_service(td->_sockfd, td->_addr);delete td;return nullptr;}~TcpServer(){}private:uint16_t _port;SockPtr _listensock;bool _isrunning;service_t _service;};
}
5.6 NetCal.hpp
這里頭文件可以直接使用 Service.hpp
,因為我們要使用 protocol.hpp
類的請求和相應類,如果 NetCal.hpp
中也包含 protocol.hpp
以及 using namespace protocol
就會導致 protocol
命名空間中的 Request
和 Response
類定義不清晰
#pragma once#include "Service.hpp"// 構建處理請求的方法類,接收請求類,返回響應類
class NetCal{public:NetCal(){}std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req){auto rsp = Factory::BuildResponseDefault();switch(req->Oper()){case '+':rsp->_result = req->X() + req->Y();break;case '-':rsp->_result = req->X() - req->Y();break;case '*':rsp->_result = req->X() * req->Y();break;case '/':if(req->Y() == 0){rsp->_result = -1;rsp->_desc = "division by zero";}else{rsp->_result = req->X() / req->Y();}break;case '%':if(req->Y() == 0){rsp->_result = -1;rsp->_desc = "mod by zero";}else{rsp->_result = req->X() % req->Y();}break;default:rsp->_result = -1;rsp->_desc = "unknown operator";break;}return rsp;}~NetCal(){}
};
5.7 TcpClient.cpp
#include "TcpClient.hpp"int main(int argc, char* argv[]){if(argc != 3){std::cerr << "Usage: " << argv[0] << " <server_ip> <server_port>" << std::endl;Die(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1. 創建套接字 并 建立連接SockPtr sock = std::make_shared<TcpSocket>();sock->BuildConnectorSocket(serverip, serverport);// srand需要的頭文件是 cstdlibsrand(time(nullptr) ^ getpid());const std::string opers = "+-*/%";std::string packmessage;while(true){// 構建數據int x = rand() % 10;usleep(x * 1000);int y = rand() % 10;usleep(x * y * 100);char oper = opers[rand() % 4];// 構建請求auto req = Factory::BuildRequestDefault();req->SetValue(x, y, oper);// 1. 序列化std::string reqstr;req->Serialize(&reqstr);// 2. 添加報頭字段reqstr = AddHeader(reqstr);std::cout << "##################################" << std::endl;std::cout << "Request String: " << reqstr << std::endl;// 3. 發送請求sock->Send(reqstr);while(true){// 4. 讀取響應ssize_t n = sock->Recv(&packmessage);if(n <= 0)break;// 5. 解析響應std::string package = ParseHeader(packmessage);if(package.empty()) continue;std::cout << "Response String: " << package << std::endl;// 6. 反序列化auto rsp = Factory::BuildResponseDefault();rsp->Deserialize(package);rsp->PrintResult();break;}sleep(10);}sock->Close();return 0;
}
5.8 TcpClient.hpp
#pragma once#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>
#include <time.h>#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include "Socket.hpp"
#include "protocol.hpp"using namespace LogModule;
using namespace SocketModule;
using namespace protocol;
5.9 Makefile
.PHONY: all
all:server_tcp client_tcpserver_tcp:TcpServer.cppg++ -o $@ $^ -std=c++17 -lpthread -ljsoncppclient_tcp:TcpClient.cpp g++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp.PHONY: clean
clean:rm -f server_tcp client_tcp
👥總結
本篇博文對 【Linux網絡】打造初級網絡計算器 - 從協議設計到服務實現 做了一個較為詳細的介紹,不知道對你有沒有幫助呢
覺得博主寫得還不錯的三連支持下吧!會繼續努力的~