0


【Linux】简单的网络计算器的实现(自定义协议,序列化,反序列化)

文章目录


前言

我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端`

一、 服务端

在这里插入图片描述

1.ServerCal.cc(服务器主文件)

#include"TcpServer.hpp"#include"ServerCal.hpp"#include<unistd.h>#include"Daemon.hpp"usingnamespace std;staticvoidUsage(const string &proc){
    cout<<"\nUsage: "<<proc<<" port\n"<<endl;}intmain(int argc,char*argv[]){if(argc!=2){Usage(argv[0]);exit(0);}uint16_t port=stoi(argv[1]);
    ServerCal cal;
    TcpServer*tsvp=newTcpServer(port,bind(&ServerCal::Calculator,&cal,placeholders::_1));//绑定服务器的回调函数
    tsvp->InitServer();//初始化服务器Daemon();//守护进程化
    tsvp->Start();//启动return0;}

2.ServerCal.hpp

#pragmaonce#include<iostream>#include"Protocol.hpp"enum{
    Div_Zero =1,
    Mod_Zero,
    Other_Oper
};classServerCal{public:ServerCal(){}
    Response CalculatorHelper(const Request &req){
        Response resp(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)
                resp.code = Div_Zero;else
                resp.result = req.x / req.y;}break;case'%':{if(req.y ==0)
                resp.code = Mod_Zero;else
                resp.result = req.x % req.y;}break;default:
            resp.code = Other_Oper;break;}return resp;}// 传入的package形式:"len"\n"10 + 20"\n
    std::string Calculator(std::string &package){
        std::string content;//Decode:去掉字符大小len和\nbool r =Decode(package,&content);// "len"\n"10 + 20"\nif(!r)return"";// Decode后:"10 + 20"

        Request req;
        r = req.Deserialize(content);// 反序列化过程:"10 + 20" ->x=10 op=+ y=20if(!r)return"";

        content ="";//进行计算  
        Response resp =CalculatorHelper(req);// result=30 code=0;//result与code进行序列化放入content
        resp.Serialize(&content);// "30 0"//Encode:序列化后加上len与\n
        content =Encode(content);// "len"\n"30 0"return content;}~ServerCal(){}};

3.Sock.hpp(套接字封装)

#pragmaonce#include<iostream>#include<string>#include<unistd.h>#include<cstring>#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
};constint backlog =10;classSock{public:Sock(){}~Sock(){}public:voidSocket(){
        sockfd_ =socket(AF_INET, SOCK_STREAM,0);if(sockfd_ <0){lg(Fatal,"socker error,%s: %d",strerror(errno), errno);exit(SocketErr);}}voidBind(uint16_t port){structsockaddr_in local;memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port =htons(port);
        local.sin_addr.s_addr = INADDR_ANY;if(bind(sockfd_,(structsockaddr*)&local,sizeof(local))<0){lg(Fatal,"bind error ,%s: %d",strerror(errno), errno);exit(BindErr);}}voidListen(){if(listen(sockfd_, backlog)<0){lg(Fatal,"listen error,%s: %d",strerror(errno), errno);exit(ListenErr);}}intAccept(string *clientip,uint16_t*clientport){structsockaddr_in peer;
        socklen_t len =sizeof(peer);int newfd =accept(sockfd_,(structsockaddr*)&peer,&len);if(newfd <0){lg(Warning,"accept error,%s: %d",strerror(errno), errno);return-1;}char ipstr[64];inet_ntop(AF_INET,&peer.sin_addr, ipstr,sizeof(ipstr));*clientip = ipstr;*clientport =ntohs(peer.sin_port);return newfd;}boolConnect(const string &ip,constuint16_t port){structsockaddr_in peer;memset(&peer,0,sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port =htons(port);inet_pton(AF_INET, ip.c_str(),&(peer.sin_addr));int n =connect(sockfd_,(structsockaddr*)&peer,sizeof(peer));if(n ==-1){
            std::cerr <<"connect to "<< ip <<":"<< port <<" error"<< std::endl;returnfalse;}returntrue;}voidClose(){close(sockfd_);}intFd(){return sockfd_;}private:int sockfd_;};

4.TcpServer.hpp(服务器)

#pragmaonce#include<functional>#include<string>#include<signal.h>#include"Log.hpp"#include"Socket.hpp"using func_t = std::function<std::string(std::string &package)>;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);//防止子进程僵尸,子进程执行完PCB自动释放signal(SIGPIPE, SIG_IGN);//程序将忽略 SIGPIPE 信号,这意味着即使发生了向已关闭的管道或者 socket //连接中写数据的情况,程序也不会因为收到 SIGPIPE 而终止。//这样可以让程序继续正常执行,而不受这种情况的影响。while(true){
            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);//调用string Calculator(std::string &package)if(info.empty())//没有读出说明数据处理失败break;lg(Debug,"debug, response:\n%s", info.c_str());lg(Debug,"debug:\n%s", inbuffer_stream.c_str());//write写入处理好后的计算过的数据(经过了序列化)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_;};

5.Protocol(自定义协议)

#pragmaonce#include<iostream>#include<string>#include<jsoncpp/json/json.h>const std::string blank_space_sep =" ";const std::string protocol_sep ="\n";//编码:给序列化后的字符串加上字符长度与\n
std::string Encode(std::string &content){
    std::string package = std::to_string(content.size());
    package += protocol_sep;
    package += content;
    package += protocol_sep;// "len"\n"x op y"\nXXXXXXreturn package;}// "protocolnumber"\n"len"\n"x op y"\nXXXXXX//目的:截取字符串“x op y”放入content中,并把与此字符串//相关的数据从总报文package中移除boolDecode(std::string &package, std::string *content){
    
    std::size_t pos = package.find(protocol_sep);if(pos == std::string::npos)returnfalse;//len_str:len的字符串形式
    std::string len_str = package.substr(0, pos);
    std::size_t len = std::stoi(len_str);// package = len_str + content_str + 2//
    std::size_t total_len = len_str.size()+ len +2;if(package.size()< total_len)returnfalse;*content = package.substr(pos+1, len);// earse 移除报文 package.erase(0, total_len);
    package.erase(0, total_len);returntrue;}// json, protobufclassRequest{public:Request(int data1,int data2,char oper):x(data1),y(data2),op(oper){}Request(){}public:boolSerialize(std::string *out){#ifdefMySelf// 构建报文的有效载荷// struct => string, "x op y"
        std::string s = std::to_string(x);
        s += blank_space_sep;
        s += op;
        s += blank_space_sep;
        s += std::to_string(y);*out = s;returntrue;#else
        Json::Value root;
        root["x"]= x;
        root["y"]= y;
        root["op"]= op;// Json::FastWriter w;
        Json::StyledWriter w;*out = w.write(root);returntrue;#endif}boolDeserialize(const std::string &in)// 目的:从"x op y"中分离出x=?,y=?,op=?填入类的成员变量{#ifdefMySelf
        std::size_t left = in.find(blank_space_sep);if(left == std::string::npos)returnfalse;
        std::string part_x = in.substr(0, left);

        std::size_t right = in.rfind(blank_space_sep);if(right == std::string::npos)returnfalse;
        std::string part_y = in.substr(right +1);if(left +2!= right)returnfalse;
        op = in[left +1];
        x = std::stoi(part_x);
        y = std::stoi(part_y);returntrue;#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        x = root["x"].asInt();
        y = root["y"].asInt();
        op = root["op"].asInt();returntrue;#endif}voidDebugPrint(){
        std::cout <<"新请求构建完成:  "<< x << op << y <<"=?"<< std::endl;}public:// x op yint x;int y;char op;// + - * / %};classResponse{public:Response(int res,int c):result(res),code(c){}Response(){}public:boolSerialize(std::string *out){#ifdefMySelf// "result code"// 构建报文的有效载荷
        std::string s = std::to_string(result);
        s += blank_space_sep;
        s += std::to_string(code);*out = s;returntrue;#else
        Json::Value root;
        root["result"]= result;
        root["code"]= code;// Json::FastWriter w;
        Json::StyledWriter w;*out = w.write(root);returntrue;#endif}boolDeserialize(const std::string &in)// "result code"{#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::Value root;
        Json::Reader r;
        r.parse(in, root);

        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具体是几,表明对应的错误原因};

二、用户端

1.ClientCal

#include<iostream>#include<string>#include<ctime>#include<cassert>#include<unistd.h>#include"Socket.hpp"#include"Protocol.hpp"staticvoidUsage(const std::string &proc){
    std::cout <<"\nUsage: "<< proc <<" serverip serverport\n"<< std::endl;}// ./clientcal ip portintmain(int argc,char*argv[]){if(argc !=3){Usage(argv[0]);exit(0);}
    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;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;// "len"\n"result code"\n
            std::cout << inbuffer_stream << std::endl;
            std::string content;bool r =Decode(inbuffer_stream,&content);// 目的:得到字符串"result code"assert(r);

            Response resp;
            。。反序列化
            r = resp.Deserialize(content);assert(r);

            resp.DebugPrint();}

        std::cout <<"================================================="<< std::endl;sleep(1);

        cnt++;}

    sockfd.Close();return0;}

三、Log.hpp(日志)

详细可参考我之前写的博客【Linux】记录错误信息日志的实现

#pragmaonce#include<iostream>#include<time.h>#include<stdarg.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<stdlib.h>usingnamespace std;#defineSIZE1024#defineInfo0#defineDebug1#defineWarning2#defineError3#defineFatal4#defineScreen1#defineOnefile2#defineClassFile3#defineLogFile"log.txt"classLog{public:Log(){
    printMethod=Screen;
    path="./log/";}voidEnable(int method){
    printMethod=method;}

 string levelToString(int level){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 string&logtxt){switch(printMethod){case Screen:
            cout<<logtxt<<endl;break;case Onefile:printOneFile(LogFile,logtxt);break;case ClassFile:printClassFile(level,logtxt);break;default:break;}}voidprintOneFile(const string&logname,const string&logtxt){
    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 string&logtxt){
    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);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;
    string path;};

Log lg;

四、makefile

.PHONY:all
all:servercal clientcal

Flag=#-DMySelf=1
Lib=-ljsoncpp

servercal:ServerCal.cc
    g++-o $@ $^-std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.cc
    g++-o $@ $^-std=c++11 $(Lib) $(Flag).PHONY:clean
clean:
    rm -f clientcal servercal
标签: linux 网络 运维

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

“【Linux】简单的网络计算器的实现(自定义协议,序列化,反序列化)”的评论:

还没有评论