0


运用自定义协议设计与实现“跨网络计算器”

阅读导航

一、设计方案

1. 日志模块 (Log.hpp)

日志模块提供了一个简单的日志记录功能,允许将日志输出到控制台、单个文件或按日志级别分类的文件中。它定义了不同级别的日志(Info, Debug, Warning, Error, Fatal),并允许通过

Enable

方法切换日志输出方式。

2. 协议模块 (Protocol.hpp)

协议模块定义了请求和响应的数据格式。

Request

类封装了计算请求的数据,包括操作数和操作符。

Response

类封装了计算结果和错误代码。这两个类都提供了序列化和反序列化的方法,以便将数据转换为网络传输的格式。

3. 服务器模块 (ServerCal.hpp、Socket.hpp 和 TcpServer.hpp )

服务器模块由

ServerCal

Socket.hpp

TcpServer

类组成。

  1. ServerCal类负责处理计算请求,它定义了一个Calculator方法来执行实际的算术运算,并返回结果和错误代码。
  2. Socket类封装了基本的socket操作,如连接、读取和写入。
  3. TcpServer类负责网络通信,它监听指定端口,接受客户端连接,并使用回调函数来处理接收到的数据。

4. 客户端模块 (ClientCal.cpp)

客户端模块提供了与服务器通信的能力,

ClientCal

类使用

Socket

类来向服务器发送计算请求,并接收响应。

客户端程序首先检查命令行参数是否正确,然后创建一个套接字并连接到服务器。程序将随机生成两个数字和一个操作符,创建一个请求对象,并将其序列化为字符串。然后,程序将这个字符串编码为网络字节流,并通过套接字发送给服务器。接收到服务器的响应后,程序将其解码并反序列化为响应对象,然后打印出请求和响应的详细信息。这个过程将重复10次,每次请求后程序会暂停1秒。最后,程序关闭套接字并退出。

二、日志模块、makefile文件

✅Log.hpp

// 预处理指令,确保头文件只被包含一次#pragmaonce// 引入必要的头文件#include<iostream>// 标准输入输出流#include<time.h>// 时间函数#include<stdarg.h>// 可变参数列表#include<sys/types.h>// 文件系统类型#include<sys/stat.h>// 文件状态#include<fcntl.h>// 文件控制#include<unistd.h>// UNIX标准函数#include<stdlib.h>// 标准库// 定义常量SIZE,用于缓冲区大小#defineSIZE1024// 定义日志级别,分别对应不同的日志重要性#defineInfo0#defineDebug1#defineWarning2#defineError3#defineFatal4// 定义日志输出方式,分别对应控制台输出和文件输出#defineScreen1#defineOnefile2#defineClassfile3// 定义日志文件名#defineLogFile"log.txt"// 日志类Log的声明classLog{public:// 构造函数,初始化日志输出方式为控制台输出,日志路径为当前目录下的log文件夹Log(){
        printMethod = Screen;// 默认输出方式为控制台输出
        path ="./log/";// 默认日志路径}// 设置日志输出方式的函数voidEnable(int method){
        printMethod = method;// 根据传入的参数设置输出方式}// 将日志级别转换为字符串的函数
    std::string levelToString(int level){// 使用switch语句根据日志级别返回对应的字符串switch(level){case Info:return"Info";case Debug:return"Debug";case Warning:return"Warning";case Error:return"Error";case Fatal:return"Fatal";default:return"None";}}// 打印日志的函数voidprintLog(int level,const std::string &logtxt){// 根据输出方式选择不同的打印方法switch(printMethod){case Screen:
                std::cout << logtxt << std::endl;// 控制台输出break;case Onefile:printOneFile(LogFile, logtxt);// 单文件输出break;case Classfile:printClassFile(level, logtxt);// 分类文件输出break;default:break;}}// 单文件日志输出的实现voidprintOneFile(const std::string &logname,const std::string &logtxt){// 拼接完整的日志文件路径
        std::string _logname = path + logname;// 打开文件,如果失败则直接返回int fd =open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND,0666);if(fd <0)return;// 将日志文本写入文件write(fd, logtxt.c_str(), logtxt.size());// 关闭文件描述符close(fd);}// 分类文件日志输出的实现voidprintClassFile(int level,const std::string &logtxt){// 根据日志级别创建对应的文件名
        std::string filename = LogFile;
        filename +=".";
        filename +=levelToString(level);// 调用单文件输出函数printOneFile(filename, logtxt);}// 析构函数,目前为空~Log(){}// 重载函数调用运算符,用于格式化输出日志voidoperator()(int level,constchar*format,...){// 获取当前时间
        time_t t =time(nullptr);structtm*ctime =localtime(&t);// 格式化时间字符串char leftbuffer[SIZE];snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d %d:%d:%d]",levelToString(level).c_str(),
                 ctime->tm_year +1900, ctime->tm_mon +1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);// 使用可变参数列表进行格式化字符串
        va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer,sizeof(rightbuffer), format, s);va_end(s);// 拼接完整的日志文本char logtxt[SIZE *2];snprintf(logtxt,sizeof(logtxt),"%s %s", leftbuffer, rightbuffer);// 调用打印日志函数printLog(level, logtxt);}private:// 日志输出方式int printMethod;// 日志文件路径
    std::string path;};// 定义一个Log类的全局对象lg,用于输出日志
Log lg;

✅makefile

.PHONY:all
all:servercal clientcal

Flag=#-DMySelf=1Lib=-ljsoncpp

servercal:ServerCal.cc
    g++ -o$@ $^ -std=c++11 $(Lib)$(Flag)
clientcal:ClientCal.cc
    g++ -o$@ $^ -std=c++11 -g$(Lib)$(Flag)

.PHONY:clean
clean:
    rm-f clientcal servercal

三、协议模块(Protocol.hpp)

✅Protocol.hpp

#pragmaonce // 确保头文件在整个程序中只被包含一次#include<iostream>// 包含标准输入输出流#include<string>// 包含字符串类#include<jsoncpp/json/json.h>// 包含JSONCPP库,用于JSON数据的处理// 宏定义,用于序列化和反序列化过程中的数据分隔const std::string blank_space_sep =" ";// 空格分隔符const std::string protocol_sep ="\n";// 换行符作为协议的分隔符// 序列化函数,将内容字符串包装成网络传输的格式
std::string Encode(std::string &content){
    std::string package;// 创建一个字符串用于存储包装后的数据
    package = std::to_string(content.size());// 将内容的长度转换为字符串
    package += protocol_sep;// 添加协议分隔符
    package += content;// 添加内容本身
    package += protocol_sep;// 再次添加协议分隔符,表示数据结束return package;// 返回包装后的字符串}// 反序列化函数,将接收到的网络数据解析为内容字符串boolDecode(std::string &package, std::string *content){
    std::size_t pos = package.find(protocol_sep);// 查找协议分隔符的位置if(pos == std::string::npos)returnfalse;// 如果找不到分隔符,解析失败

    std::string len_str = package.substr(0, pos);// 提取长度字符串
    std::size_t len = std::stoi(len_str);// 将长度字符串转换为数字
    std::size_t total_len = len_str.size()+ len +2;// 计算总长度(长度字符串 + 内容 + 分隔符)if(package.size()< total_len)returnfalse;// 如果实际数据长度小于预期,解析失败*content = package.substr(pos+1, len);// 提取内容字符串
    package.erase(0, total_len);// 从原始数据中移除已解析的部分returntrue;// 解析成功}// 请求数据结构classRequest{public:// 构造函数,初始化请求的参数Request(int data1,int data2,char oper):x(data1),y(data2),op(oper){}// 默认构造函数Request(){}public:// 序列化方法,将请求对象转换为字符串boolSerialize(std::string *out){#ifdefMySelf// 使用简单的字符串拼接方式
        std::string s = std::to_string(x);// 将操作数x转换为字符串
        s += blank_space_sep;// 添加空格分隔符
        s += op;// 添加操作符
        s += blank_space_sep;// 再次添加空格分隔符
        s += std::to_string(y);// 将操作数y转换为字符串*out = s;// 将拼接后的字符串赋值给输出参数returntrue;// 返回成功#else// 使用JSON格式
        Json::Value root;// 创建JSON值对象
        root["x"]= x;// 添加操作数x
        root["y"]= y;// 添加操作数y
        root["op"]= op;// 添加操作符
        Json::StyledWriter w;// 创建JSON格式化写入器*out = w.write(root);// 将JSON对象转换为格式化的字符串returntrue;// 返回成功#endif}// 反序列化方法,将字符串转换为请求对象boolDeserialize(const std::string &in){#ifdefMySelf// 使用简单的字符串拼接方式
        std::size_t left = in.find(blank_space_sep);// 查找第一个空格分隔符if(left == std::string::npos)returnfalse;// 如果找不到分隔符,解析失败
        std::string part_x = in.substr(0, left);// 提取操作数x的字符串

        std::size_t right = in.rfind(blank_space_sep);// 查找最后一个空格分隔符if(right == std::string::npos)returnfalse;// 如果找不到分隔符,解析失败
        std::string part_y = in.substr(right +1);// 提取操作数y的字符串if(left +2!= right)returnfalse;// 如果分隔符之间的长度不符合预期,解析失败
        op = in[left +1];// 提取操作符
        x = std::stoi(part_x);// 将操作数x的字符串转换为数字
        y = std::stoi(part_y);// 将操作数y的字符串转换为数字returntrue;// 返回成功#else// 使用JSON格式
        Json::Value root;
        Json::Reader r;if(!r.parse(in, root))returnfalse;// 解析JSON字符串,如果失败则返回

        x = root["x"].asInt();// 提取操作数x
        y = root["y"].asInt();// 提取操作数y
        op = root["op"].asInt();// 提取操作符returntrue;// 返回成功#endif}// 调试方法,打印请求对象的内容voidDebugPrint(){
        std::cout <<"新请求构建完成:  "<< x << op << y <<"=?"<< std::endl;// 打印操作数和操作符}public:// 请求的数据成员int x;// 操作数xint y;// 操作数ychar op;// 操作符,可以是 + - * / %};// 响应数据结构classResponse{public:// 构造函数,初始化响应的参数Response(int res,int c):result(res),code(c){}// 默认构造函数Response(){}public:// 序列化方法,将响应对象转换为字符串boolSerialize(std::string *out){#ifdefMySelf// 使用简单的字符串拼接方式
        std::string s = std::to_string(result);// 将结果转换为字符串
        s += blank_space_sep;// 添加空格分隔符
        s += std::to_string(code);// 将错误代码转换为字符串*out = s;// 将拼接后的字符串赋值给输出参数returntrue;// 返回成功#else// 使用JSON格式
        Json::Value root;// 创建JSON值对象
        root["result"]= result;// 添加结果
        root["code"]= code;// 添加错误代码
        Json::StyledWriter w;// 创建JSON格式化写入器*out = w.write(root);// 将JSON对象转换为格式化的字符串returntrue;// 返回成功#endif}// 反序列化方法,将字符串转换为响应对象boolDeserialize(const std::string &in){#ifdefMySelf// 使用简单的字符串拼接方式
        std::size_t pos = in.find(blank_space_sep);// 查找空格分隔符if(pos == std::string::npos)returnfalse;// 如果找不到分隔符,解析失败
        std::string part_left = in.substr(0, pos);// 提取结果字符串
        std::string part_right = in.substr(pos +1);// 提取错误代码字符串

        result = std::stoi(part_left);// 将结果字符串转换为数字
        code = std::stoi(part_right);// 将错误代码字符串转换为数字returntrue;// 返回成功#else// 使用JSON格式
        Json::Value root;
        Json::Reader r;if(!r.parse(in, root))returnfalse;// 解析JSON字符串,如果失败则返回

        result = root["result"].asInt();// 提取结果
        code = root["code"].asInt();// 提取错误代码returntrue;// 返回成功#endif}// 调试方法,打印响应对象的内容voidDebugPrint(){
        std::cout <<"结果响应完成, result: "<< result <<", code: "<< code << std::endl;// 打印结果和错误代码}public:int result;// 计算结果int code;// 错误代码,0表示成功,非0表示错误};

四、服务端模块

✅ServerCal.hpp

// 预处理指令,确保头文件只被包含一次#pragmaonce// 引入必要的头文件#include<iostream>// 引入自定义的协议头文件#include"Protocol.hpp"// 定义枚举类型,用于表示不同类型的操作和错误enum{
    Div_Zero =1,// 除数为零的错误代码
    Mod_Zero,// 模数为零的错误代码
    Other_Oper     // 其他操作或者错误};// 声明ServerCal类,用于处理客户端的计算请求classServerCal{public:// 构造函数ServerCal(){}// 计算助手函数,根据请求计算结果并返回响应对象
    Response CalculatorHelper(const Request &req){
        Response resp(0,0);// 创建一个响应对象,初始化结果和错误代码为0// 根据请求中的操作符进行计算switch(req.op)// 检查操作符{case'+':// 加法
                resp.result = req.x + req.y;break;case'-':// 减法
                resp.result = req.x - req.y;break;case'*':// 乘法
                resp.result = req.x * req.y;break;case'/':// 除法{if(req.y ==0)// 如果除数为0,设置错误代码
                    resp.code = Div_Zero;else// 否则进行除法运算
                    resp.result = req.x / req.y;}break;case'%':{if(req.y ==0)// 如果模数为0,设置错误代码
                    resp.code = Mod_Zero;else// 否则进行模运算
                    resp.result = req.x % req.y;}break;default:// 如果操作符不是预定义的几种
                resp.code = Other_Oper;// 设置错误代码为其他操作break;}return resp;// 返回计算结果和错误代码}// 计算函数,解析请求字符串,并返回计算结果
    std::string Calculator(std::string &package){
        std::string content;// 用于存储解码后的内容// 解析请求包的长度和内容bool r =Decode(package,&content);if(!r)return"";// 如果解码失败,返回空字符串// 从解码后的内容中反序列化请求对象
        Request req;
        r = req.Deserialize(content);if(!r)return"";// 如果反序列化失败,返回空字符串// 清空content,准备存储响应内容
        content ="";// 调用助手函数进行计算
        Response resp =CalculatorHelper(req);// 将计算结果和错误代码序列化到content
        resp.Serialize(&content);// 编码响应内容,添加长度前缀
        content =Encode(content);return content;// 返回响应字符串}// 析构函数~ServerCal(){}};

✅Socket.hpp

// 预处理指令,确保头文件只被包含一次#pragmaonce// 引入必要的头文件#include<iostream>// 标准输入输出流#include<string>// 字符串类#include<unistd.h>// UNIX标准函数,如sleep等#include<cstring>// C字符串处理函数#include<sys/types.h>// 系统类型定义#include<sys/stat.h>// 文件状态信息#include<sys/socket.h>// 套接字相关函数和结构定义#include<arpa/inet.h>// 网络地址转换#include<netinet/in.h>// 网络接口定义#include"Log.hpp"// 自定义的日志库// 定义枚举类型,用于表示不同的错误代码enum{
    SocketErr =2,// 套接字创建错误
    BindErr,// 套接字绑定错误
    ListenErr    // 套接字监听错误};// 定义backlog常量,用于listen函数的参数constint backlog =10;// Sock类声明,封装了套接字操作的一系列方法classSock{public:// 构造函数Sock(){}// 析构函数~Sock(){}// 创建套接字的方法voidSocket(){// 使用socket函数创建一个流式套接字,地址族为IPv4,类型为TCP
        sockfd_ =socket(AF_INET, SOCK_STREAM,0);// 如果套接字创建失败,记录错误日志并退出程序if(sockfd_ <0){lg(Fatal,"socket error, %s: %d",strerror(errno), errno);exit(SocketErr);}}// 绑定套接字到指定端口的方法voidBind(uint16_t port){// 创建一个sockaddr_in结构体,用于绑定操作structsockaddr_in local;memset(&local,0,sizeof(local));// 清零结构体
        local.sin_family = AF_INET;// 设置地址族为IPv4
        local.sin_port =htons(port);// 设置端口号,使用网络字节序
        local.sin_addr.s_addr = INADDR_ANY;// 允许绑定到所有可用网络接口// 使用bind函数将套接字绑定到指定端口,如果失败记录错误日志并退出程序if(bind(sockfd_,(structsockaddr*)&local,sizeof(local))<0){lg(Fatal,"bind error, %s: %d",strerror(errno), errno);exit(BindErr);}}// 使套接字监听传入连接的方法voidListen(){// 使用listen函数使套接字监听传入连接,backlog参数指定最大的连接请求队列长度if(listen(sockfd_, backlog)<0){lg(Fatal,"listen error, %s: %d",strerror(errno), errno);exit(ListenErr);}}// 接受传入连接的方法intAccept(std::string *clientip,uint16_t*clientport){// 创建一个sockaddr_in结构体,用于存储客户端的地址信息structsockaddr_in peer;
        socklen_t len =sizeof(peer);// 使用accept函数接受一个传入连接,返回一个新的套接字描述符int newfd =accept(sockfd_,(structsockaddr*)&peer,&len);// 如果接受连接失败,记录警告日志并返回-1if(newfd <0){lg(Warning,"accept error, %s: %d",strerror(errno), errno);return-1;}// 将客户端的IP地址和端口号转换为可读格式char ipstr[64];inet_ntop(AF_INET,&peer.sin_addr, ipstr,sizeof(ipstr));*clientip = ipstr;*clientport =ntohs(peer.sin_port);// 转换端口号为主机字节序return newfd;// 返回新的套接字描述符}// 连接到指定的IP地址和端口号的方法boolConnect(const std::string &ip,constuint16_t&port){// 创建一个sockaddr_in结构体,用于存储目标地址信息structsockaddr_in peer;memset(&peer,0,sizeof(peer));// 清零结构体
        peer.sin_family = AF_INET;// 设置地址族为IPv4
        peer.sin_port =htons(port);// 设置端口号,使用网络字节序inet_pton(AF_INET, ip.c_str(),&(peer.sin_addr));// 将IP地址转换为网络字节序// 使用connect函数尝试连接到目标地址,如果失败则记录错误并返回falseint n =connect(sockfd_,(structsockaddr*)&peer,sizeof(peer));if(n ==-1){
            std::cerr <<"connect to "<< ip <<":"<< port <<" error"<< std::endl;returnfalse;}returntrue;// 连接成功返回true}// 关闭套接字的方法voidClose(){close(sockfd_);// 使用close函数关闭套接字}// 获取套接字文件描述符的方法intFd(){return sockfd_;// 返回套接字文件描述符}private:int sockfd_;// 套接字文件描述符};

✅TcpServer.hpp

// 预处理指令,确保头文件只被包含一次#pragmaonce// 引入必要的头文件#include<functional>// 用于支持 std::function#include<string>// 用于支持 std::string#include<signal.h>// 用于处理信号#include"Log.hpp"// 自定义的日志头文件#include"Socket.hpp"// 自定义的套接字头文件// 定义一个类型别名,用于简化函数参数using func_t = std::function<std::string(std::string &package)>;// 声明TcpServer类,用于创建和运行TCP服务器classTcpServer{public:// 构造函数,初始化服务器的端口号和回调函数TcpServer(uint16_t port, func_t callback):port_(port),callback_(callback){}// 初始化服务器的函数,创建套接字,绑定端口,开始监听boolInitServer(){// 创建监听套接字
        listensock_.Socket();// 绑定端口到套接字
        listensock_.Bind(port_);// 开始监听连接请求
        listensock_.Listen();// 记录服务器初始化信息lg(Info,"init server .... done");returntrue;}// 启动服务器的主函数,处理客户端连接请求voidStart(){// 忽略子进程退出和管道信号signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);// 循环处理连接请求while(true){// 接受客户端的连接请求,获取客户端的IP地址和端口号
            std::string clientip;uint16_t clientport;int sockfd = listensock_.Accept(&clientip,&clientport);// 如果接受连接失败,则继续下一次循环if(sockfd <0)continue;// 记录客户端连接信息lg(Info,"accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);// 处理客户端请求,创建子进程提供服务if(fork()==0){// 关闭监听套接字
                listensock_.Close();
                std::string inbuffer_stream;// 循环读取客户端发送的数据while(true){// 读取数据到缓冲区char buffer[1280];
                    ssize_t n =read(sockfd, buffer,sizeof(buffer));// 如果读取到数据if(n >0){// 将读取的数据添加到输入缓冲区
                        buffer[n]=0;
                        inbuffer_stream += buffer;// 记录调试信息lg(Debug,"debug:\n%s", inbuffer_stream.c_str());// 循环处理输入缓冲区中的数据while(true){// 调用回调函数处理数据,并获取响应信息
                            std::string info =callback_(inbuffer_stream);// 如果没有响应信息,则跳出循环if(info.empty())break;// 记录调试信息lg(Debug,"debug, response:\n%s", info.c_str());// 记录调试信息lg(Debug,"debug:\n%s", inbuffer_stream.c_str());// 将响应信息发送回客户端write(sockfd, info.c_str(), info.size());}}// 如果客户端关闭连接,则跳出循环elseif(n ==0)break;// 如果读取失败,则跳出循环elsebreak;}// 子进程服务结束后退出exit(0);}// 关闭客户端套接字close(sockfd);}}// 析构函数~TcpServer(){}private:// 服务器的端口号uint16_t port_;// 监听套接字
    Sock listensock_;// 回调函数,用于处理客户端请求
    func_t callback_;};

✅ServerCal.cpp

// 引入必要的头文件#include"TcpServer.hpp"// 引入自定义的TCP服务器头文件#include"ServerCal.hpp"// 引入自定义的计算器逻辑头文件#include<unistd.h>// 引入UNIX标准函数库,用于系统调用如sleep等// #include "Daemon.hpp" // 引入自定义的守护进程头文件(当前被注释)// 定义Usage函数,用于输出程序的使用方法staticvoidUsage(const std::string &proc){
    std::cout <<"\nUsage: "<< proc <<" port\n"<< std::endl;// 输出程序的使用方法}// 主函数,程序的入口点intmain(int argc,char*argv[]){// 检查命令行参数数量是否正确if(argc !=2){Usage(argv[0]);// 如果参数不正确,输出使用方法并退出程序exit(0);}// 将命令行参数转换为端口号uint16_t port = std::stoi(argv[1]);// 创建计算器逻辑对象
    ServerCal cal;// 创建TCP服务器对象,绑定端口号和计算器逻辑对象的Calculator方法
    TcpServer *tsvp =newTcpServer(port, std::bind(&ServerCal::Calculator,&cal, std::placeholders::_1));// 初始化服务器
    tsvp->InitServer();// 调用守护进程相关函数,将程序转为守护进程运行(当前被注释)// Daemon();daemon(0,0);// 调用守护进程函数,参数设置为0表示不进行标准输出和错误输出的重定向// 启动服务器,开始监听和处理客户端请求
    tsvp->Start();return0;// 程序正常结束}

五、客户端模块

✅ClientCal.cpp

// 引入所需的头文件#include<iostream>// 用于输入输出流#include<string>// 用于字符串类#include<ctime>// 用于时间相关函数#include<cassert>// 用于断言检查#include<unistd.h>// 用于UNIX标准函数,如sleep#include"Socket.hpp"// 自定义的套接字操作库#include"Protocol.hpp"// 自定义的通信协议库// 定义Usage函数,用于输出程序的使用方法staticvoidUsage(const std::string &proc){
    std::cout <<"\nUsage: "<< proc <<" serverip serverport\n"<< std::endl;}// 主函数,程序的入口点intmain(int argc,char*argv[]){// 检查命令行参数数量是否正确if(argc !=3){Usage(argv[0]);exit(0);}// 从命令行参数获取服务器的IP地址和端口号
    std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 创建套接字对象
    Sock sockfd;// 创建套接字
    sockfd.Socket();// 尝试连接到服务器bool r = sockfd.Connect(serverip, serverport);if(!r)return1;// 如果连接失败,则退出程序// 初始化随机数生成器srand(time(nullptr)^getpid());// 定义测试次数int cnt =1;// 定义操作符字符串const std::string opers ="+-*/%=-=&^";// 定义输入缓冲区流
    std::string inbuffer_stream;// 循环发送请求并接收响应,直到发送了10次while(cnt <=10){// 输出测试次数信息
        std::cout <<"===============第"<< cnt <<"次测试....., "<<"==============="<< std::endl;// 生成随机数作为请求的参数int x =rand()%100+1;usleep(1234);// 微秒级的暂停int y =rand()%100;usleep(4321);// 微秒级的暂停// 随机选择一个操作符char oper = opers[rand()% opers.size()];// 创建请求对象
        Request req(x, y, oper);// 打印请求的详细信息(调试用)
        req.DebugPrint();// 序列化请求对象到字符串
        std::string package;
        req.Serialize(&package);// 将请求字符串编码为网络字节流
        package =Encode(package);// 通过套接字发送请求write(sockfd.Fd(), package.c_str(), package.size());// 读取服务器的响应char buffer[128];
        ssize_t n =read(sockfd.Fd(), buffer,sizeof(buffer));// 读取响应到缓冲区if(n >0){// 确保读取的字符串以空字符结尾
            buffer[n]=0;// 将读取的内容添加到输入缓冲区流
            inbuffer_stream += buffer;// 输出接收到的完整响应
            std::cout << inbuffer_stream << std::endl;// 从输入缓冲区流中解码出响应内容
            std::string content;bool r =Decode(inbuffer_stream,&content);// 解码响应内容assert(r);// 断言解码成功// 反序列化响应内容到Response对象
            Response resp;
            r = resp.Deserialize(content);assert(r);// 断言反序列化成功// 打印响应的详细信息(调试用)
            resp.DebugPrint();}// 输出测试分隔线
        std::cout <<"================================================="<< std::endl;// 暂停一秒sleep(1);// 增加测试次数
        cnt++;}// 关闭套接字
    sockfd.Close();// 程序正常退出return0;}

六、设计方案总结

  1. 模块化:代码被分为不同的模块,每个模块负责一个特定的功能。这种设计使得代码易于理解和维护。
  2. 日志记录:通过Log类,服务器和客户端可以记录操作信息和错误信息,有助于调试和监控。
  3. 自定义协议:通过Protocol类,定义了一个简单的文本协议来传输请求和响应。协议的设计简单明了,易于理解和实现。
  4. 错误处理:服务器和客户端都实现了基本的错误处理逻辑,能够处理一些常见的错误情况。
  5. 多进程模型TcpServer类使用多进程模型来处理并发连接,每个客户端连接都在自己的进程中运行。
  6. 安全性:虽然代码中没有明确提到安全性措施,但在实际部署时,应该考虑使用加密通信、身份验证等安全措施来保护数据。
  7. 性能优化:在生产环境中,可能需要进一步优化服务器的性能,比如使用多线程或异步I/O代替多进程模型,或者使用专门的高性能网络库。
  8. 可扩展性:代码设计允许未来添加新的功能,比如支持更多的操作符或增加新的操作类型。

通过上述设计方案,我们可以得到一个基础的TCP服务器和客户端,它们可以根据自定义协议处理客户端请求,并支持基本的算术运算。这个系统可以作为“跨网络计算器”的基础,通过定义合适的回调函数和协议格式来实现计算器的功能。


本文转载自: https://blog.csdn.net/m0_75215937/article/details/137526287
版权归原作者 Yawesh 所有, 如有侵权,请联系我们删除。

“运用自定义协议设计与实现“跨网络计算器””的评论:

还没有评论