【Linux網絡】打造初級網絡計算器 - 從協議設計到服務實現

📢博客主頁: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 報頭

在實際的網絡通信中,傳的不僅僅是序列化后的字符串,還有報頭信息,此處我們也設計一下報頭信息

  1. "len"\r\n"{json}"\r\n – 完整的報文
  2. len 有效荷載長度
  3. \r\n(第一個):區分 len 和 json 串
  4. \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 命名空間中的 RequestResponse 類定義不清晰

#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網絡】打造初級網絡計算器 - 從協議設計到服務實現 做了一個較為詳細的介紹,不知道對你有沒有幫助呢

覺得博主寫得還不錯的三連支持下吧!會繼續努力的~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/78324.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78324.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/78324.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

計算機視覺——對比YOLOv12、YOLOv11、和基于Darknet的YOLOv7的微調對比

概述 目標檢測領域取得了巨大進步&#xff0c;其中 YOLOv12、YOLOv11 和基于 Darknet 的 YOLOv7 在實時檢測方面表現出色。盡管這些模型在通用目標檢測數據集上表現卓越&#xff0c;但在 HRSC2016-MS&#xff08;高分辨率艦船數據集&#xff09; 上對 YOLOv12 進行微調時&…

?MySQL 事務隔離級別詳解

? 以下是 MySQL 支持的四種事務隔離級別及其特性&#xff0c;按并發安全性從低到高排列&#xff1a; ?1. 讀未提交 (Read Uncommitted)? ?問題?&#xff1a; ?臟讀 (Dirty Read)?&#xff1a;事務可讀取其他事務未提交的數據。?不可重復讀 (Non-repeatable Read)?&am…

如何解決IDE項目啟動報錯 error:0308010C:digital envelope routines::unsupported 問題

如何解決IDE項目啟動報錯 error:0308010C:digital envelope routines::unsupported 問題 在現代軟件開發過程中&#xff0c;開發人員通常使用集成開發環境&#xff08;IDE&#xff09;如IntelliJ IDEA、Visual Studio Code&#xff08;VSCode&#xff09;等進行Node.js項目開發…

2025最新Facefusion3.1.2使用Docker部署,保姆級教程,無需配置環境

Docker部署Facefusion 環境 windows10 Facefusion3.1.2 安裝 拉取源代碼 git clone https://github.com/facefusion/facefusion-docker.git 此處如果拉不下來&#xff0c;需要科學上網&#xff0c;不會的可以找我。 運行容器 將Dockerfile.cpu文件中的的From python:3.…

docker容器監控自動恢復

關于實現對docker容器監控以及自動恢復&#xff0c;這里介紹兩種實現方案。 方案1&#xff1a; 實現思路&#xff1a; 找到&#xff08;根據正則表達式&#xff09;所有待監控的docker容器&#xff0c;此處篩選邏輯根據docker運行狀態找到已停止&#xff08;Exit&#xff09;類…

HackMyVM - Chromee靶機

HackMyVM - chromee靶機https://mp.weixin.qq.com/s/hF09_24PRXpx_lmB6dzWVg

Cursor中調用本地大語言模型

引言 隨著大語言模型(LLM)技術的快速發展&#xff0c;越來越多的開發者希望在本地環境中運行這些強大的AI模型&#xff0c;以獲得更好的隱私保護、更低的延遲以及不依賴網絡連接的使用體驗。Cursor作為一款面向開發者的AI增強編輯器&#xff0c;提供了與本地大語言模型集成的功…

青少年CTF-貪吃蛇

題目描述&#xff1a; 進入賽題頁面&#xff1a; 按F12&#xff0c;查看源代碼&#xff0c; 可以看到是當分數大于或等于10000時&#xff0c;獲得flag&#xff0c;值已經給出&#xff0c;直接引用就可以&#xff0c;check_score.php?score${score}&#xff0c;這里將${score}換…

亞馬遜測評老砍單?了解過全新自養號系統嗎?

以全球電商巨頭亞馬遜為例&#xff0c;其風控技術的進化堪稱一部永不停歇的“升級史”。然而&#xff0c;令人遺憾的是&#xff0c;不少賣家和測評服務商卻依舊沉浸在過去的“舒適區”&#xff0c;過度依賴指紋瀏覽器、luminati等傳統技術手段。這些曾經行之有效的工具&#xf…

module.noParse(跳過指定文件的依賴解析)

1. 說明 module.noParse 是 Webpack 的一個配置項&#xff0c;用于跳過對指定模塊的解析。通過忽略某些文件的依賴分析&#xff0c;可以提升構建速度&#xff0c;尤其適用于處理大型、獨立的第三方庫 2. 使用配置 webpakc.config.js const path require(path); module.exp…

什么是爬蟲?——從技術原理到現實應用的全面解析 V

什么是爬蟲?——從技術原理到現實應用的全面解析 V 二十一、云原生爬蟲架構設計 21.1 無服務器爬蟲(AWS Lambda) # lambda_function.py import boto3 import requests from bs4 import BeautifulSoups3 = boto3.client(s3)def lambda_handler(event, context):# 抓取目標…

Web滲透之系統入侵與提權維權

滲透測試步驟 信息收集 搜集一些IP地址以及對應的端口開放情況&#xff0c;看看是否有80、3306、22等等端口開放&#xff0c;以及操作系統和版本號&#xff0c;同時也要掃描可能存在的漏洞 漏洞利用 建立據點 漏洞利用成功后&#xff0c;通常會在目標機上獲得一個webshell&…

【數論分塊】數論分塊算法模板及真題

1.數論分塊的含義 數論分塊算法&#xff0c;就是枚舉出使得取整函數發生變化的地方。 例如&#xff0c;對表達式 ? n i ? \lfloor \frac{n}{i} \rfloor ?in??使用數論分塊算法&#xff0c;就可以在 O ( n ) O(\sqrt n) O(n ?)的時間復雜度下枚舉所有滿足 ? n i ? 1 ?…

SpringBoot 常用注解通俗解釋

SpringBoot 常用注解通俗解釋 一、啟動類相關 1. SpringBootApplication ? 作用&#xff1a;這是SpringBoot項目的"總開關"&#xff0c;放在主類上 ? 通俗理解&#xff1a;相當于對電腦說&#xff1a;"開機&#xff01;我要用SpringBoot了&#xff01;…

棧應用:括號匹配

1&#xff1a;普通字符串括號匹配 #include <iostream> #include <stack> #include <string> using namespace std; bool mat(char,char); int if_match(string); int main(){string a;cin>>a;cout<<if_match(a)<<endl;return 0; } bool m…

某東h5st_5.1(補環境)

JS逆向實戰——某東h5st_5.1&#xff08;補環境&#xff09; 聲明網站流程分析結果展示總結 聲明 本文章中所有內容僅供學習交流&#xff0c;抓包內容、敏感網址、數據接口均已做脫敏處理&#xff0c;嚴禁用于商業用途和非法用途&#xff0c;否則由此產生的一切后果均與作者無…

新增Webhook通知功能,文檔目錄樹展示性能優化,zyplayer-doc 2.5.1 發布啦!

zyplayer-doc是一款適合企業和個人使用的WIKI知識庫管理工具&#xff0c;支持在線編輯富文本、Markdown、表格、Office文檔、API接口、思維導圖、Drawio以及任意的文本文件&#xff0c;支持基于知識庫的AI問答&#xff0c;專為私有化部署而設計&#xff0c;最大程度上保證企業或…

macOS安全隱私最佳實踐分析

1. 引言 隨著數字世界的不斷擴展&#xff0c;個人和組織面臨的安全與隱私威脅也日益增加。作為專業的安全合規與隱私保護研究團隊&#xff0c;Kaamel 對 macOS 系統的安全隱私現狀進行了全面分析&#xff0c;并提出了一系列最佳實踐建議&#xff0c;旨在幫助用戶更好地保護自己…

架構設計之異地多活與單元化(Set化)

公司的業務到達一定規模后,往往會考慮做多數據中心。一方面是面臨業務增長帶來的挑戰,單個數據中心變得難以支撐;另一方面出于對業務容災的考量,也可能在多個城市建立數據中心達到容災目的。單元化(Set化)是作為異地多活的一個解決方案。 一、什么是異地多活 異地多活是…

Kettle學習

一、Kettle 簡介 Kettle(現稱為 Pentaho Data Integration)是一款開源ETL工具,支持從多種數據源抽取、轉換和加載數據,廣泛應用于數據倉庫構建、數據遷移和清洗。其核心優勢包括: 可視化操作:通過拖拽組件設計數據處理流程(轉換和作業)。多數據源支持:數據庫(MySQL/…