【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
- 👥总结
11111111
11111111
11111111
11111111
**** 11111111
🏳️🌈一、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网络】打造初级网络计算器 - 从协议设计到服务实现 做了一个较为详细的介绍,不知道对你有没有帮助呢
觉得博主写得还不错的三连支持下吧!会继续努力的~
相关文章:
【Linux网络】打造初级网络计算器 - 从协议设计到服务实现
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
基于STM32定时器中断讲解(HAL库)
基于STM32定时器中断讲解(HAL库) 1、定时器简单介绍 以STM32F103C8T6中几个定时器为例: TIM1:这是一个高级定时器,不仅具备基本的定时中断功能,还拥有内外时钟源选择、输入捕获、输出比较、编码器接口以…...
《Vue3学习手记5》
pinia 共享的数据交给集中状态管理 引入与使用 //main.ts // 引入Pinia import {createPinia} from "pinia"const piniacreatePinia() app.use(pinia)案例: <template><div class"count"><h2>当前和为:{{ sum…...
MySQL多查询条件下深度分页性能优化技巧及示例总结
深度分页(Deep Pagination)是MySQL中常见的性能瓶颈问题,特别是在多查询条件下,当offset值很大时,查询性能会急剧下降。本文将总结多种优化技巧,并提供实际示例。 一、深度分页的性能问题分析 当执行类似SELECT * FROM table WHERE condition1 AND condition2 LIMIT 1000…...
3、初识RabbitMQ
界面上的导航栏共分6部分,分别代表不同的意思 一、Producer和Consumer Producer: 生产者, 是RabbitMQ Server的客户端, 向RabbitMQ发送消息 Consumer: 消费者, 也是RabbitMQ Server的客⼾端, 从RabbitMQ接收消息 Broker:其实就是RabbitMQ Server, 主要…...
量子计算与GPU的异构加速:基于CUDA Quantum的混合编程实践
一、量子模拟的算力困境与GPU破局 量子计算模拟面临指数级增长的资源需求:n个量子比特的态向量需要存储2^n个复数。当n>30时,单机内存已无法承载(1TB需求)。传统CPU模拟器(如Qiskit Aer)在n28时计算…...
在Spring Boot项目中实现Word转PDF并预览
在Spring Boot项目中实现Word转PDF并进行前端网页预览,你可以使用Apache POI来读取Word文件,iText或Apache PDFBox来生成PDF文件,然后通过Spring Boot控制器提供文件下载或预览链接。以下是一个示例实现步骤和代码: 1. 添加依赖 …...
Windows怎样使用curl下载文件
安装curl 从官网下载:访问curl官方网站,根据系统位数(32 位或 64 位)选择相应的版本进行下载。下载完成后,双击安装程序并按照提示进行安装。也可以选择自定义安装路径,记住安装路径,后续配置环…...
priority_queue的学习
priority_queue的介绍 优先级队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。优先队列被…...
浅谈Java 内存管理:栈与堆,垃圾回收
在Java编程世界里,内存管理是一项极为关键的技能,它就像程序运行背后的“隐形守护者”,默默影响着程序的性能与稳定性。今天,咱们就来简单学习一下Java内存管理中的两大核心要点:栈与堆的内存分配机制,以及…...
windows下查看idea运行的进程占的JVM情况工具
jconsole 查看JVM 查看线程数 自己测试时,可以先不把线程关闭查效果。 也可以用这工具查下是不是有线程一直在增加。...
【新技术】微软 Azure Test Impact Analyzer (TIA) 全面解析
目录 一、什么是 Azure Test Impact Analyzer?二、核心功能与优势三、如何掌握 Azure TIA?四、工作中的典型应用场景五、最佳实践与注意事项六、总结 一、什么是 Azure Test Impact Analyzer? Azure Test Impact Analyzer (TIA) 是微软 Azur…...
JAVA服务内存缓慢上涨,年轻代GC正常但Full GC频繁,如何定位?
1. 分析 : 年轻代GC正常,说明年轻代的对象回收没有问题,可能大部分对象都是朝生夕死的,所以Minor GC能有效清理。但Full GC频繁,通常意味着老年代空间不足,导致频繁进行Full GC来回收老年代。而内存缓慢上…...
浏览器界面无显示,提示“代理服务器可能有问题”,这是怎么回事呢?
前言 🌟🌟本期讲解浏览器代理服务器解决办法介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话不…...
C#中的弱引用使用
弱引用(Weak Reference)是一种特殊的引用类型,它允许你引用一个对象,但不会阻止该对象被垃圾回收器(GC)回收。弱引用通常用于需要缓存或跟踪对象,但又不希望因保留引用而导致内存泄漏的场景。弱…...
在Linux虚拟机下使用vscode,#include无法跳转问题
总结:需要通过Linux指令来添加编译器和压缩文件,解压,这样获得的编译器会具有可执行权限类似于 -rwxr-xr-x 1 user user 12345 Apr 26 14:22 myscript.sh 如果你直接从window中拖入文件到Linux文件下,你需要自己来再度开启可编译…...
MIL、SIL、HIL与Back-to-Back测试详解:从模型到硬件的完整验证链
1. 引言 在嵌入式系统和控制算法开发中,MIL、SIL、HIL和Back-to-Back测试构成了从模型设计到硬件部署的完整验证流程。它们覆盖不同开发阶段,确保系统功能正确性、实时性和可靠性。 本文将清晰解析这四种测试方法的核心概念、应用场景及差异。 2. 四种测…...
【Android Compose】焦点管理
官方文档链接: https://developer.android.google.cn/develop/ui/compose/touch-input/focus?hlzh-cn 1、更改焦点遍历顺序 1.1、替换一维遍历顺序 (1)创建焦点引用对象: /// 创建4个引用对象(二选一)…...
启动命令汇总(Redis / Kafka / Flume / Spark)
本文总结了本地开发环境(Windows系统)中启动推荐系统所需的所有组件命令,包括 Redis、Kafka、Flume 及 SparkStreaming 程序的启动流程。 1. 启动 Redis 进入 Redis 安装目录,执行: redis-server.exe测试连接&#x…...
python 画折线统计图
Python 画折线统计图(line chart)最常用的是 matplotlib。 最基本的折线图代码如下: import matplotlib.pyplot as plt# 假设这是你的数据 x [1, 2, 3, 4, 5] y [2, 3, 5, 7, 11]# 创建折线图 plt.plot(x, y, markero) # markero 是在点…...
java面向对象编程【高级篇】之继承
目录 🚀前言🤔什么是继承?🌟权限修饰符💯private 修饰符💯默认(无修饰符)💯protected 修饰符💯public 修饰符💯归纳 🦜继承的特点&…...
【数论分块】数论分块算法模板及真题
1.数论分块的含义 数论分块算法,就是枚举出使得取整函数发生变化的地方。 例如,对表达式 ⌊ n i ⌋ \lfloor \frac{n}{i} \rfloor ⌊in⌋使用数论分块算法,就可以在 O ( n ) O(\sqrt n) O(n )的时间复杂度下枚举所有满足 ⌊ n i − 1 ⌋…...
DIY 3D打印机 原理及步骤概况
一、3D打印机的基本原理 硬件组成: 运动系统:控制X/Y/Z轴的步进电机(或直线电机),决定打印头的移动精度。 热端(挤出机):加热并挤出材料(如PLA、ABS塑料)。 …...
深度探索:DeepSeek赋能WPS图表绘制
一、研究背景 在当今数字化信息爆炸的时代,数据处理与可视化分析已成为众多领域研究和决策的关键环节。随着数据量的急剧增长和数据维度的不断丰富,传统的数据可视化工具在应对复杂数据时逐渐显露出局限性。Excel作为广泛应用的电子表格软件,…...
内存四区(栈)
今天我再次学到了有趣的知识,内存四区! 内存四区分为代码区,全局区,栈区,堆区,今天我们详细来讲讲栈区! 内存四区和栈区都是用来存放数据的,而栈区存放的数据具体有两类 1.形参数…...
Nginx性能优化:从配置到缓存,全面提升Web服务器性能
一、基础配置优化:释放硬件潜能 进程与连接调优 worker_processes: 推荐设置为 auto(自动匹配CPU核心数),但在特殊场景下需手动优化:worker_processes 8; # 8核CPU手动指定 worker_cpu_affinity 000…...
系统架构设计(三):质量属性
常见分类 一般来说,质量属性可以分为以下几类: 类别常见质量属性性能相关响应时间、吞吐量、资源利用率、实时性、可扩展性可用性相关可用性、高可用性(HA)、可靠性、容错性、恢复性可维护性相关可维护性、可测试性、可扩展性、…...
C#中常见的设计模式
文章目录 引言设计模式的分类创建型模式 (Creational Patterns)1. 单例模式 (Singleton)2. 工厂方法模式 (Factory Method)3. 抽象工厂模式 (Abstract Factory)4. 建造者模式 (Builder) 结构型模式 (Structural Patterns)5. 适配器模式 (Adapter)6. 装饰器模式 (Decorator)7. 外…...
C# 枚举(Enum)声明与使用详解
在 C# 编程中,枚举(Enum)是一种非常实用的数据类型,它允许你定义一组具有名称的整型常量,使代码更具可读性和可维护性。枚举可以有效地替代使用硬编码数值,尤其是在处理状态、选项或标志时。本文将深入探讨…...
Linux-进程控制
目录 一、进程创建 1.1、fork()函数 1.2、fork的返回值 1.3、写实拷贝(Copy-on-Write,COW) 1.4、fork常规用法 1.5、fork调用失败的原因 二、进程退出 三、进程等待 1、wait和waitpid 1.1、解决僵尸进程问题 1.2、status参数 程序正…...
【优选算法 | 滑动窗口】滑动窗口算法:高效处理子数组和子串问题
算法相关知识点可以通过点击以下链接进行学习一起加油!双指针 在本篇文章中,我们将深入剖析滑动窗口算法的核心原理。从基础概念到实战应用,带你了解如何利用滑动窗口高效解决连续子数组和子串等问题。无论你是算法入门的新手,还是…...
RabbitMQ全栈实践手册:从零搭建消息中间件到SpringAMQP高阶玩法
目录 前言 认识MQ 同步调用 异步调用 技术选型 安装 SpringAMQP 交换机类型 队列交换机绑定 环境搭建 Fanout交换机 声明队列和交换机 消息发送 消息接收 总结 Direct交换机 声明队列和交换机 消息发送 消息接收 总结 Topic交换机 声明队列和交换机 消息…...
头歌实训之存储过程、函数与触发器
🌟 各位看官好,我是maomi_9526! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习C语言的相关知识。 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更…...
系统架构设计中的DSSA方法:理论、实践与行业深度应用
引言 在软件架构设计领域,DSSA(Domain-Specific Software Architecture,领域特定软件架构)是一种专注于垂直行业或业务领域的架构设计方法论。与通用架构设计不同,DSSA通过提炼领域共性需求、构建可复用资产库&am…...
设计心得——数据结构的意义
一、数据结构 在老一些的程序员中,可能都听说过,程序其实就是数据结构算法这种说法。它是由尼克劳斯维特在其著作《算法数据结构程序》中提出的,然后在一段时期内这种说法非常流行。这里不谈论其是否正确,只是通过这种提法&#…...
【C】初阶数据结构12 -- 冒泡排序
本篇文章主要讲解经典排序算法 -- 冒泡排序。 目录 1 算法思想 2 代码 3 时间复杂度与空间复杂度分析 1) 时间复杂度 2) 空间复杂度 1 算法思想 选择排序是一种经典的交换排序算法。其算法思想也比较简单,主要是比较相邻元素&…...
HTTP, AMQP, MQTT之间的区别和联系是什么?华为云如何适配?
目录 🔗 一、共同点(联系): 🔍 二、区别对比: 📘 三、简要说明 1. HTTP 2. AMQP 3. MQTT 🔗 四、三者联系(在华为云IoT平台中的应用) 🎯 …...
WPF之项目创建
文章目录 引言先决条件创建 WPF 项目步骤理解项目结构XAML 与 C# 代码隐藏第一个 "Hello, WPF!" 示例构建和运行应用程序总结相关学习资源 引言 Windows Presentation Foundation (WPF) 是 Microsoft 用于构建具有丰富用户界面的 Windows 桌面应用程序的现代框架。它…...
CrewAI Community Version(二)——Agent
目录 1. Agent总览2. Agent属性3. 创建Agent3.1 YAML配置3.2 直接用代码定义3.3 运行结果 参考 1. Agent总览 在CrewAI框架中,Agent是一个能具备下列能力的自主单元: 1. 执行特定的任务 2. 基于它的角色和目标进行决策 3. 使用工具完成任务 …...
阿里云VS AWS中国区:ICP备案全攻略与常见误区解析
导语 在中国大陆开展互联网服务时,ICP备案是必不可少的合规步骤。然而,随着云服务的多样化,许多企业在选择备案路径时常常感到困惑。本文将深入解析阿里云和AWS中国区的备案区别,为您提供清晰的操作指南,助您避开备案陷阱,确保业务合规运营。 一、备案基本原则 1. 服务器决定…...
基于libdxfrw库读取样条曲线并离散为点
在计算机辅助设计(CAD)与制造(CAM)领域,DXF(Drawing Exchange Format)格式文件被广泛用于存储与交换矢量图形信息。样条曲线作为DXF文件中常见的复杂曲线类型,其准确读取与离散化处理…...
学习 Apache Kafka
学习 Apache Kafka 是一个很好的选择,尤其是在实时数据流处理和大数据领域。以下是一个系统化的学习建议,帮助你从入门到进阶掌握 Kafka: 1. 先决条件 在开始 Kafka 之前,确保你具备以下基础: Java 基础:K…...
5.3/Q1,GBD数据库最新文章解读
文章题目:The burden and trend prediction of ischemic heart disease associated with lead exposure: Insights from the Global Burden of Disease study 2021 DOI:10.1186/s12940-025-01155-w 中文标题:与铅暴露相关的缺血性心脏病的负担…...
java智慧城管综合管理系统源码,前端框架:vue+element;后端框架:springboot;移动端:uniapp开发,技术前沿,可扩展性强
智慧城管综合执法系统采用B/S模式设计与手机等移动终端架构,采用 java编程语言前端框架:vueelement;后端框架:springboot;数据库:mysql5.7;移动端:uniapp技术开发设计。具有使用与维…...
【锂电池剩余寿命预测】GRU门控循环单元锂电池剩余寿命预测(Matlab完整源码)
目录 效果一览程序获取程序内容代码分享研究内容GRU门控循环单元在锂电池剩余寿命预测中的应用摘要关键词1. 引言1.1 研究背景1.2 研究现状与问题1.3 研究目的与意义2. 文献综述2.1 锂电池剩余寿命预测传统方法2.2 深度学习在锂电池寿命预测中的应用2.3 研究空白与本文切入点3.…...
开发首个Spring Boot应用
📋 前置条件 🎯 在开始之前,请打开终端并运行以下命令以确保已安装正确版本的 Java: $ java -version openjdk version "17.0.4.1" 2022-08-12 LTS OpenJDK Runtime Environment (build 17.0.4.11-LTS) OpenJDK 64-Bi…...
2025第十六届蓝桥杯大赛(软件赛)网络安全赛 Writeup
2025第十六届蓝桥杯大赛(软件赛)网络安全赛 Writeup 2025第十六届蓝桥杯大赛(软件赛)网络安全赛 Writeup情报收集黑客密室逃脱 数据分析ezEvtxflowzip 密码破解EnigmaECBTraineasy_AES 逆向分析ShadowPhases 漏洞挖掘分析RuneBrea…...
HTTP 协议深度解析:从基础到实战的完整指南
HTTP(HyperText Transfer Protocol)是 应用层协议,用于客户端(浏览器、APP)与服务器之间的数据交互。以下从协议原理、核心机制到实际案例全面解析,涵盖 HTTP/1.1 到 HTTP/3 的演进。 一、HTTP 核心特性 …...
5G助力智慧城市的崛起——从概念到落地的技术实践
5G助力智慧城市的崛起——从概念到落地的技术实践 引言:智慧城市中的“隐形脉络” 随着城市化的快速推进,传统的城市管理方式已经难以满足人口增长和资源优化的需求。智慧城市的概念应运而生,通过技术创新实现智能化、可持续发展的城市生态…...
4.25test
R7-5 小黄与研究生会(20) 分数 12 全屏浏览 切换布局 作者 王秀 单位 福州大学 福州大学研究生院怡山的同学们为了在国家对抗新冠疫情期间献出自己的一份力量,他们决定为奋战在一线的医护人员送去了演出。小黄作为研究生协会的会长,他让每位男同学均带去了若干只猫或狗…...