【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.mzph.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/…