【计算机网络】序列化与反序列化

news2025/1/13 7:35:05

文章目录

  • 1. 如何处理结构化数据?
    • 序列化 与 反序列化
  • 2. 实现网络版计算器
    • 1. Tcp 套接字的封装——sock.hpp
      • 创建套接字——Socket
      • 绑定——Bind
      • 将套接字设置为监听状态——Listen
      • 获取连接——Accept
      • 发起连接——Connect
    • 2. 服务器的实现 ——TcpServer.hpp
      • 初始化
      • 启动
        • 多线程的使用
    • 3. 自定义协议定制——Protocol.hpp
      • Request的自定义序列化
      • Request的自定义反序列化
      • Until.hpp (存放 StringSlit | toInt 函数)
        • StringSlit——将字符串存放入数组中
        • toInt——字符串转化为整数
      • Response的自定义序列化
      • Response的自定义反序列化
    • 4. Tcpserver.hpp的调用
      • 1.如何保证读到完整的字符串报文?
        • ReadPackage的实现
      • 2.获取有效载荷部分
        • RmoveHeader的实现
      • 3. 假设已经读到完整的sring
      • 4.提取用户的请求数据
      • 5. 给用户响应——序列化
      • 6.添加报头
      • 7. 发送
  • 3. 整体代码实现
    • Util.hpp(单独存放两个函数的实现)
    • makefile
    • TcpServer.hpp (Tcp服务端 已封装)
    • Tcpclient.hpp(未封装)
    • Sock.hpp(Tcp套接字)
    • Protocol.hpp(序列化与反序列化)
    • log.hpp(日志)
    • err.hpp(报错)
    • CalculatorServer.cc (服务端 主函数)
    • CalculatorClient.cc (客户端 主函数)

1. 如何处理结构化数据?

通过打包的方式,将结构体message发送给对方
对方收到后就会报告给上层QQ客户端

结构化的数据 是由 多个 string 构成的

而以前在网络套接字 发送时,都是按照一个字符串的方式来发送和接收的


序列化 与 反序列化

所以想办法 ,把多个字符串 转化为 一个大"字符串",对方在接收时也是一个长的字符串,
再想办法把这个字符串转回结构化的数据,就可以让上层使用

把一个结构化的数据 转化为 一个长的字符串 的 过程 称之为 序列化
把一个长的字符串 转化为 一个结构化的数据的 过程 称之为 反序列化

2. 实现网络版计算器

实现一个服务器版的加法器,把客户端把要计算的两个加数发过去,由服务器计算,最后把结果返回给客户端

1. Tcp 套接字的封装——sock.hpp

Sock.hpp 表示 对Tcp套接字的封装

设置一个私有变量 监听套接字 (与accept返回的文件描述符 进行区分)


创建套接字——Socket

输入 man socket,创建套接字

第一个参数 domain ,用于区分 进行网络通信还是 本地通信
若想为网络通信,则使用 AF_INET
若想为本地通信,则使用 AF_UNIX


第二个参数 type, 套接字对应的服务类型

在这里插入图片描述

SOCK_STREAM 流式套接
SOCK_DGRAM 无连接不可靠的通信(用户数据报)

第三个参数 protocol ,表示想用那种协议,协议默认为0
若为 流式套接,则系统会认为是TCP协议 ,若为用户数据报,则系统会认为是UDP协议

套接字的返回值:若成功则返回文件描述符,若失败则返回 -1


使用socket 创建一个TCP的网络通信,并返回文件描述符到 _listensock中
把上篇博客的 日志(log.hpp)与错误信息枚举(err.hpp)拷贝过来
若套接字创建失败,则通过日志将错误信息打印处来,并借助 错误信息枚举 终止程序

绑定——Bind

输入 man 2 bind ,查看绑定

给一个套接字绑定一个名字
第一个参数 sockfd 为 套接字
第二个参数 addr 为 通用结构体类型
第三个参数 addrlen 为 第二个参数的实际长度大小

bind返回值:若成功,则返回0,若失败,返回 -1


想要使用bind函数,就需要先创建一个网络通信类型的变量,通过该变量存储端口号 IP地址 16位地址类型
所以要先定义一个 struct sockaddr_in(网络通信) 类型的 变量 local

htons 主机序列转化为 网络序列
需要借助 htons 将传进来的参数 port端口号进行转化
INADDR_ANY 表示 本机的所有IP


若小于0,则绑定失败
依旧使用日志打印处错误码和错误原因,再终止程序


将套接字设置为监听状态——Listen

输入 man 2 listen
设置当前套接字状态为 监听状态

第一个参数 sockfd 为 套接字
第二个参数 暂不做解释,一般设为整数
若成功则返回0,若失败返回-1


若小于0,则监听失败
依旧使用日志打印处错误码和错误原因,再终止程序

获取连接——Accept

输入 man 2 accept

需要知道谁连的你,所以要获取到客户端的相关信息

第一个参数 sockfd 为套接字
第二个参数 addr 为通用结构体类型的 结构体 这个结构体是用来记录客户端内的port号以及IP地址 、16位地址类型等信息
第三个参数 addrlen 为 结构体的大小

返回值:
若成功,则返回一个合法的整数 即文件描述符
若失败,返回-1并且设置错误码


sock 这个文件描述符 是真正给用户提供IO服务的
若连接失败,则返回-1,使用日志将错误信息打印出来


若连接成功,则需获取到对应的客户端的 端口号 与客户端的IP地址
使用 inet_ntoa 4字节风格IP转化为字符串风格IP
使用 ntohs 网络序列转主机序列


发起连接——Connect

connect 函数功能为客户端主动连接服务器
成功返回0,失败返回-1

2. 服务器的实现 ——TcpServer.hpp

使用Sock这个类,实例化对象_listensock

初始化

在初始化中,使用_listensock这个对象 去访问 Scok类中实现过的 Socket Bind Listen 等函数

启动

作为一款服务器,就需要一直运行 作数据的分析

通过_listensock对象访问Accept函数获取客户端的IP地址和端口号

多线程的使用

在类中的函数如果不加static修饰,就会导致存在隐藏的this指针
所以 回调函数 需加 static 修饰


使用 pthread_join 默认是阻塞的 ,即主线程等待 新线程退出
在这个过程中,主线程会直接卡住,就没办法继续向后运行,也就什么都干不了
若主线程 想做其他事情 ,所以就提出了线程分离的概念


创建一个结构体ThreadData内部包含sock套接字以及一个指向服务器的指针 ip地址 port端口号

在初始化 多线程部分,new对象,将sock clientip client port 与this指针传递过去作为参数 完成构造


再将td传过去作为回调函数的参数


在回调函数内部调用 serviceIO函数 来完成协议

3. 自定义协议定制——Protocol.hpp

在命名空间Protocol_n中,定义两个类,分别为Request类和Reponse类

若读到 字符串风格的Request ,就需要通过 序列化 转成 结构化的数据

Request的自定义序列化

自己定义 将结构化的数据 转化为 字符串
假设空格作为分割符


使用to_string 将任意类型转化为string


使用 宏, 将SEP表示为空格

将_x _y _op 使用空格连接起来

Request的自定义反序列化

提供一个函数StringSplit ,去掉字符串中的空格,分别填入vector数组中,作为vetcor数组中的元素
下标为0开始的位置 填入_x ,下标为1开始的位置 填入 _op
下标为2开始的位置 填入 _y


借助函数 toInt,将string类型的元素 转化为 整数

_op在 vector数组的1号下标中,对应其中的一个字符


Until.hpp (存放 StringSlit | toInt 函数)

StringSlit——将字符串存放入数组中

寻找SEP分割符所在位置,即可分割出区间
使用find函数,从start位置开始寻找分隔符sep,找到分割符sep后,将区间内的子串插入vector数组中

当sep为空格时,只占用一个位置,pos处于空格位置 ,只需加1即可跳出空格
故start的位置 只需 从pos 位置 加上 sep长度即可得到

若出了循环str中依旧有子串没有被插入vector中,则全部当做一个整体放入vector中

toInt——字符串转化为整数

使用 atoi 函数 将字符串转化为 整形


Response的自定义序列化

使用to_string 将任意类型转化为string

将 res_string SEP 和 code_string 连接起来

Response的自定义反序列化

同样取调用 StringSplit函数 将字符串 转换为 vector数组中的元素
分别将结果和错误码提取出来

4. Tcpserver.hpp的调用

1.如何保证读到完整的字符串报文?

定义一个string类型的package,从套接字sock读取,将结果添加到package中
若有完整报文就交给package,没有完整报文,则一直读取
inbuffer 用于记录报文的所有数据


ReadPackage的实现

输入 man recv

第一个参数为 套接字
第二个参数为缓冲区
第三个参数 为缓冲区长度
第四个参数为 读取方式 ,一般默认为0
返回值为读取到的字节数,若字节数小于0,则表示读取出错


先使用recv,将sock中的数据读取到buffer中,再将数据传入inbuffer中


通过find 查找inbuffer中的\r\n的位置,在使用substr将提取到的头部字符串(报头) ,
使用 toInt 将字符串转化为数字 ,即获取到字符串长度
最终将有效载荷数据传入 package中


若返回值为-1,则表示读取失败,若返回值为0,则表示继续读取
若返回值为1,则表示读取成功,即可进入下面步骤

2.获取有效载荷部分

RmoveHeader的实现

从后面先减去一个分隔符,再减去有效载荷的长度
从有效载荷位置开始 取 有效载荷的长度个字符 即 取到有效载荷


3. 假设已经读到完整的sring

构建一个Request 对象
通过该对象去访问请求的 反序列化 ,将字符串str转化为结构化的数据

4.提取用户的请求数据

定义一个包装器,其返回值类型为Response ,参数为 Request ,并重命名为 func_t


使用func_t类型 定义 一个func的私有成员变量


将Request处理完 变为 Response


在Calculatorserver.cc中,进行请求处理

在这里插入图片描述


先将结果与错误码默认都设置为0,表示成功
使用 switch case 把request变量的req 中的 _x _y 通过 加 减 乘 除 取模 等进行运算
若期间错误码 出现 1 2 3,则表示错误
最终 将执行后的结果 返回resp中

5. 给用户响应——序列化

对response结构进行序列化,将其转化为字符串

6.添加报头

将send_string字符串 中 添加字符串长度 分隔符 \r\n

7. 发送

输入 man send

第一个参数为 套接字
第二个参数为特定字符串数据
第三个参数为 数据长度
第四个参数为 默认为0


3. 整体代码实现

Util.hpp(单独存放两个函数的实现)

#pragma once 
#include<iostream>
#include<string>
#include<vector>
#include<cstdlib>

using namespace std;
class Util 
{
 public:
     //将字符串str 按照sep分隔符 把结果放入 result中
     //分割成功 则为true  分割失败,则为false
    static bool  StringSplit(const string &str,const string &sep,std::vector<string>*result)
    {
        size_t  start=0;
        // 10 + 20
        while(start<str.size())
        {
         auto pos=str.find(sep,start);//从start位置开始寻找sep
         if(pos==string::npos)//找不到分隔符了
         {
            break;
         }
         //从start位置开始 寻找pos-start个字符,并将其放入vector数组中
         result->push_back(str.substr(start,pos-start));
        
         //位置的重新加载
         start=pos+sep.size();
        }
        
        //若出了循环str中依旧有子串没有被插入vector中,则全部当做一个整体放入
        if(start<str.size())
        {
        result->push_back(str.substr(start));
        }
        return true;
    }

     //字符串转整数
     static int  toInt(string &s)
    {
      return atoi(s.c_str());
    }
};


makefile

.PHONY:all
all:calserver calclient

calclient:CalculatorClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread  -ljsoncpp
calserver:CalculatorServer.cc
	g++ -o $@ $^ -std=c++11 -lpthread  -ljsoncpp

.PHONY:clean
clean:
	rm -f  calclient calserver	

TcpServer.hpp (Tcp服务端 已封装)

#pragma once 
#include<iostream>
#include<pthread.h>
#include<functional> 
#include"Sock.hpp"
#include"Protocol.hpp"
//服务器

namespace  tcpserver_ns
{
  using namespace protocol_ns;
   class TcpServer;

   //定义包装器
   using func_t =std::function<Response(const Request&)>;
   class ThreadData
   {
    public:
     ThreadData(int sock,std::string ip,uint16_t port,TcpServer*tsvrp)//构造
     :_sock(sock),_ip(ip),_port(port),_tsvrp(tsvrp)
     {}
     ~ThreadData()
     {}
    public:
    int _sock;//套接字
    TcpServer *_tsvrp;//指针指向Tcp服务器
    std::string _ip;
    uint16_t   _port;
    };


   class TcpServer
  {
   public:
     TcpServer(func_t func,uint16_t port ):_func(func),_port(port)
     {}
     void InitServer()//初始化
     {
          //1.初始化服务器
          _listensock.Socket();//创建套接字
          _listensock.Bind(_port);//绑定
          _listensock.Listen();//监听
          logmessage(INFO,"init server done,listensock:%d",_listensock.Fd());
     }

     //该函数被多线程调用
     void serviceIO(int sock,const std::string ip,const uint16_t port)//提供服务
     {

       std::string inbuffer;//用于记录报文的所有数据
      while(true)
      {
        //1.如何保证读到完整的字符串报文? ----7\r\n""10 + 20"\r\n
          //不能保证
         //所以要一直循环读取,边读取 边检测 测试

         std::string package; 
         //从sock中读,将结果添加到package
         int n=ReadPackage(sock,inbuffer,&package);
         if(n==-1) //-1表示读取失败
         {
          break;;
         }
         else if(n==0)//0表示继续读
         {
           continue;
         }
         else //读取成功,返回报头长度
         {

          //2.需要的只是有效载荷的部分  "10 + 20"
          package=RemoveHeader(package,n);//将package中的有效载荷提取出来

        
  

           //3.假设已经读到一个完整的string
           Request req; //构建一个Request对象 
           std::string str;
           req.Deserialize(str);//对读到的request字符串进行反序列化

          //4.提取用户的请求数据
           Response resp= _func(req);

           //5.给用户返回响应
          std::string send_string;
          resp.Serialize(&send_string);//对计算完毕的response结构进行序列化,形成可发送字符串  
        
          //6. 添加报头
          send_string =AddHeader(send_string);//添加报头

          //7. 发送
          send(sock,send_string.c_str(),send_string.size(),0);
         }
      }
      close(sock);

     }

     static void*ThreadRoutine(void*args)
     {
       pthread_detach(pthread_self());//线程分离
       ThreadData* td=(ThreadData*)args;
       td->_tsvrp->serviceIO(td->_sock,td->_ip,td->_port);
       delete td;
       return nullptr;
     }
      
     void Start()//启动
     {
         for(; ;)
         {
            std::string  clientip;
             uint16_t clientport;
            int sock=_listensock.Accept(&clientip,&clientport);//获取连接
            if(sock<0)//连接失败
            {
                continue;
            }
            logmessage(DEBUG,"get a new client,client info:[%s:%d]",clientip.c_str(),clientport);
            
            //多线程
            pthread_t tid;
            ThreadData*td=new ThreadData(sock,clientip,clientport,this);
            pthread_create(&tid,nullptr, ThreadRoutine,td);
         }
     }
     ~TcpServer()
     {
       _listensock.Close();
     }
   private:
    func_t _func;
    uint16_t _port;//端口号
    Sock _listensock;
 };
}

Tcpclient.hpp(未封装)

#pragma once
#include<iostream>
#include<string>



Sock.hpp(Tcp套接字)

#pragma once 
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
#include"log.hpp"
#include"err.hpp"

static const int  gbacklog=32;
static const int defaultfd=-1;
class Sock
{
 public:
 Sock() //构造
 :_sock(defaultfd)
 {
 }

 void  Socket()//创建套接字
 {
  _sock=socket(AF_INET,SOCK_STREAM,0);
  if(_sock<0)//套接字创建失败
  {
    logmessage(FATAL,"socket error,code:%s,errstring:%s",errno,strerror(errno));
    exit(SOCKET_ERR);
  }
 }

  void Bind(uint16_t port)//绑定
  {
   struct sockaddr_in local;
   memset(&local,0,sizeof(local));//清空
   local.sin_family=AF_INET;//16位地址类型
   local.sin_port= htons(port); //端口号
   local.sin_addr.s_addr= INADDR_ANY;//IP地址
   
   //若小于0,则绑定失败
   if(bind(_sock,(struct sockaddr*)&local,sizeof(local))<0)
   {
      logmessage(FATAL,"bind error,code:%s,errstring:%s",errno,strerror(errno));
      exit(BIND_ERR);
   }
  }
   
   void Listen()//将套接字设置为监听状态
   {
      //小于0则监听失败
      if(listen(_sock,gbacklog)<0)
      {
        logmessage(FATAL,"listen error,code:%s,errstring:%s",errno,strerror(errno));
        exit(LISTEN_ERR);
      }
   }

   int Accept(std::string *clientip,uint16_t * clientport)//获取连接
   {
        struct sockaddr_in temp;
        socklen_t len=sizeof(temp);
        int sock=accept(_sock,(struct sockaddr*)&temp,&len);

        if(sock<0)
        {
             logmessage(WARNING,"accept error,code:%s,errstring:%s",errno,strerror(errno));
        }
        else 
        {
            //inet_ntoa 4字节风格IP转化为字符串风格IP
            *clientip = inet_ntoa(temp.sin_addr) ; //客户端IP地址
            //ntohs 网络序列转主机序列
            *clientport= ntohs(temp.sin_port);//客户端的端口号
            

        }
        return sock;//返回新获取的套接字
   }

   int Connect(const std::string&serverip,const uint16_t &serverport )//发起链接
   {
      struct sockaddr_in server;
      memset(&server,0,sizeof(server));//清空
      server.sin_family=AF_INET;//16位地址类型
      server.sin_port=htons(serverport);//端口号
      //inet_addr  字符串风格IP转化为4字节风格IP
      server.sin_addr.s_addr=inet_addr(serverip.c_str());//IP地址
      //成功返回0,失败返回-1
      return  connect(_sock, (struct sockaddr*)&server,sizeof(server));
    
    }

    int Fd()
    {
      return _sock;
    }
    void Close()
    {
      if(_sock!=defaultfd)
     {
       close(_sock);
     }

    }
    
 ~Sock()//析构
 {
    
 }
 private:
 int _sock;

};

Protocol.hpp(序列化与反序列化)

#pragma once 
#include<iostream>
#include<string>
#include<cstring>
#include<vector>
#include<jsoncpp/json/json.h>
#include"Util.hpp"

//#define MYSELF 1 //用于条件编译
//给网络版本计算器定制协议
namespace protocol_ns
{

#define SEP " "
#define SEP_LEN strlen(SEP)
#define HEADER_SEP "\r\n"
#define  HEADER_SEP_LEN strlen("\r\n")
//"长度"\r\n "_x _op _y"\r\n 
//Request与 Response 都要提供序列化和反序列化功能

// "10 + 20" -> "7\r\n""10 + 20"\r\n
std::string AddHeader(const std::string&str)//添加报头
{
    std::string s=std::to_string(str.size());//字符串的长度
    s+= HEADER_SEP;//加上分隔符
    s+= str;//加上正文
    s+= HEADER_SEP;
}

// "7\r\n""10 + 20"\r\n"  ->  "10 + 20" 
std::string RemoveHeader(const std::string &str,int len)//提取数据
{
  return str.substr(str.size()-HEADER_SEP_LEN-len,len);//获取有效载荷
}

// 0表示继续读  1表示读取成功  -1表示读取失败
 int  ReadPackage(int sock,std::string& inbuffer,std::string* package)
 {
    //边读取
     char buffer[1024];
     ssize_t s=recv(sock,&buffer,sizeof(buffer-1),0);//将sock中的数据读取到buffer中
     if(s<=0)//读取出错
     {
        return -1;
     }
     buffer[s]=0;
     inbuffer += buffer;

   //边分析  7\r\n""10 + 20"\r\n
    auto pos=inbuffer.find( HEADER_SEP);//查询\r\n的位置
    if(pos==std::string::npos)//没有找到\r\n,则说明报文不完整
    {
        return 0;
    }
   
   std::string lenstr=inbuffer.substr(0,pos);//获取头部字符串
   int len =Util::toInt(lenstr);//有效载荷长度 "123"-> 123  
   int targetPackageLen=lenstr.size() + len+2* HEADER_SEP_LEN;//总报文=报头长度+有效载荷长度+分隔符长度
   if(inbuffer.size()<targetPackageLen)
   //说明缓冲区不存在完整报文
   {
      return 0;
   }
   *package= inbuffer.substr(0,targetPackageLen); //提取有效载荷数据
   inbuffer.erase(0,targetPackageLen);//从inbuffer中移除整个报文
   return len;//返回有效载荷长度
 
 }

    class Request//请求
    {
    public:
       Request()
       {}
       Request(int x,int y,char op)
       :_x(x),_y(y),_op(op)
       {}
       //序列化 结构化的数据 转为字符串
       bool Serialize( std::string* outstr)
       {
           *outstr="";//清空
#ifdef MYSELF
         // _x _op _y
         *outstr="";//清空
         std::string x_string =std::to_string(_x);
         std::string y_string =std::to_string(_y);

         //手动序列化
           *outstr=x_string + SEP + _op + SEP + y_string; 
#else
           Json::Value root;//value:一种万能对象,接收任意的kv类型
           root["x"]=_x;
           root["y"]=_y;
           root["op"]=_op;
           
           Json::FastWriter writer; // write 用于进行序列化 将结构化字段转化为字符串
           //Json::StyledWriter
           *outstr =writer.write(root);

#endif

           return true;
          
       }

       //反序列化 字符串 转为 结构化的数据
       bool Deserialize(const std::string &instr)
       {
#ifdef  MYSELF
         std::vector<std::string> result;
         Util::StringSplit(instr,SEP,&result);
         //根据协议规定必须等于3  _x _op _y
         if(result.size()!=3)
         {
            return false;
         }
         _x=Util::toInt(result[0]);
         _y=Util::toInt(result[2]);
         if(result[1].size()!=1)
         {
            return false;
         }
          _op= result[1][0];

#else 
          Json::Value root;
         Json::Reader reader;//Reader 用于进行反序列化
         reader.parse(instr,root);//将结果放入root中

         _x=root["x"].asInt();//将字符串类型转换为整形
         _y=root["y"].asInt();
         _op=root["op"].asInt();//转化为数字 放入char中,最后会被解释为字符

#endif 

       }
       ~Request()
       {}
    public:
      // _x op _y
      //x y为操作数  op为操作符
      int _x;
      int _y;
      char _op;
    };

    class  Response//响应
    {
    public:
       Response()
       {}
        Response(int result,int code)
        :_result(result),_code(code)
       {}

        //序列化 结构化的数据 转为字符串
       bool Serialize( std::string* outstr)
       {
#ifdef MYSELF
*outstr="";//清空
         std::string res_string =std::to_string(_result);
         std::string code_string =std::to_string(_code);
         *outstr=res_string + SEP + code_string;
#else 
         Json::Value root;
         root["reslut"]=_result;
         root["code"]  =_code;

         Json::FastWriter writer;//用于反序列化
         *outstr=writer.write(root);

#endif 
         return true;

       }
       //反序列化 字符串 转为 结构化的数据
       bool Deserialize(const std::string &instr)
       {
#ifdef MYSELF
          // 10 0 / 10 1 / 10 2 
           std::vector<std::string> result;
           Util::StringSplit(instr,SEP,&result);
          //当前只存在 结果和错误码
           if(result.size()!=2)
          {
            return false;
          }
           _result=Util::toInt(result[0]);
           _code=Util::toInt(result[1]);
#else 
        Json::Value root;
        Json::Reader  reader;//用于反序列化
        reader.parse(instr,root);//将结果传给root
        _result=root["result"].asInt();//将字符串结果转化为整数
        _code=root["code"].asInt();
#endif 
            return true;
       }
       
       ~Response()
       {}
    public:

       int _result;//结果
       int _code;//默认为0 表示成功 1 2 3 4 不同的数字表示不同的错误码
    };
}

log.hpp(日志)

#pragma once 
#include<iostream>
#include<string.h>
#include<cstdio>
#include<cstring>
#include<cstdarg>
#include<unistd.h>
#include<sys/types.h>
#include<time.h>

const std::string  filename="tecpserver.log";

//日志等级
enum{
 DEBUG=0, // 用于调试
 INFO  ,  //1 常规
 WARNING, //2 告警
 ERROR ,  //3  一般错误
 FATAL ,  //4 致命错误
 UKNOWN//未知错误
};

static  std::string tolevelstring(int level)//将数字转化为字符串
{
  switch(level)
  {
     case DEBUG : return "DEBUG";
     case INFO  : return "INFO";
     case WARNING : return "WARNING";
     case  ERROR : return "ERROR";
     case FATAL : return "TATAL";
     default: return "UKNOWN";
  }
}
std::string gettime()//获取时间
{
   time_t curr=time(nullptr);//获取time_t
   struct tm *tmp=localtime(&curr);//将time_t 转换为 struct tm结构体
   char buffer[128];
   snprintf(buffer,sizeof(buffer),"%d-%d-%d %d:%d:%d",tmp->tm_year+1900,tmp->tm_mon+1,tmp->tm_mday,
   tmp->tm_hour,tmp->tm_min,tmp->tm_sec);
   return buffer;

}
void logmessage(int level, const char*format,...)
{
   //日志左边部分的实现
   char logLeft[1024];
   std::string level_string=tolevelstring(level);
   std::string curr_time=gettime();
   snprintf(logLeft,sizeof(logLeft),"%s %s %d",level_string.c_str(),curr_time.c_str());

   //日志右边部分的实现
   char logRight[1024]; 
   va_list p;//p可以看作是1字节的指针
   va_start(p,format);//将p指向最开始
   vsnprintf(logRight,sizeof(logRight),format,p);
   va_end(p);//将指针置空
   
   //打印日志 
   printf("%s%s\n",logLeft,logRight);
}


err.hpp(报错)

#pragma once 

enum
{
  USAGE_ERR=1,
  SOCKET_ERR,//2
  BIND_ERR,//3
  LISTEN_ERR,//4
  SETSID_ERR,//5
  OPEN_ERR//6
};



CalculatorServer.cc (服务端 主函数)

 #include"TcpServer.hpp"
 #include<memory>

using namespace tcpserver_ns;
 // ./calserver 8888
 Response calculate(const Request& req)
 {
    //一定保证req 是有具体数据的
    //默认将结果和错误码设置 为0
    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=1;
      }
      else 
      {
       resp._result= req._x / req._y;
      }
      break;
    case '%':
      if(req._y==0)
      {
       resp._code=2;
      }
      else 
      {
       resp._result= req._x % req._y;
      }
        break;
    default:
     resp._code=3;
     break;

    }
    return resp;  
 } 

 int main()
 {  
  uint16_t port=8888;
  std::unique_ptr<tcpserver_ns::TcpServer> tsvr(new tcpserver_ns::TcpServer(calculate,port));
  tsvr->InitServer();
  tsvr->Start();
    return 0;
 }

CalculatorClient.cc (客户端 主函数)

#include"TcpClient.hpp"
#include<iostream>
#include<string>
#include "Sock.hpp"
#include"Protocol.hpp"

using namespace protocol_ns;
static void usage(std::string proc)
{
   std::cout<<"usage:\n\t"<< proc<<" serverip serverport\n"<<std::endl;
}

//./tcpclient  serverip serverport
 int main(int argc,char*argv[])
 {
   if(argc!=3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);//终止程序
    }
    std::string serverip= argv[1];
    uint16_t serverport=atoi(argv[2]);//将字符串转化为整形


    Sock sock;
    sock.Socket();//创建套接字

    int n=sock.Connect(serverip,serverport);//发起连接
    if(n!=0)//连接失败
    {
      return 1;
    }
    
    std::string buffer;
    while(true)
    {
      std::cout<<" enter# "<<std::endl;//1+1
      std::string line;
      std::getline(std::cin,line);


      Request req;
      std::cout<<"data#1"<<std::endl;
      std::cin>>req._x;

      std::cout<<"data#2"<<std::endl;
      std::cin>>req._y;

      std::cout<<"op#3"<<std::endl;
      std::cin>>req._op;

      std::cout<<req._x<<req._op<<req._y<<std::endl;

      //1.序列化
      std::string sendstring;
      req.Serialize(&sendstring);
      
      //2.添加报头
      AddHeader(sendstring); 

      //3.发送
      send(sock.Fd(),sendstring.c_str(),sendstring.size(),0);

      //4.获得响应
      std::string package;
      //返回有效载荷长度 若大于0则继续执行
      int n=0;
      START:
      n=ReadPackage(sock.Fd(),buffer,&package);
      if(n==0)
      {
         goto START;
      }
      else if(n<0)
      {
        break;
      }
      else 
      {}

      //5. 去掉报头
      package=RemoveHeader(package,n);//获取有效载荷
      
      //6.反序列化
       Response  resp;
       resp.Deserialize(package);//反序列化
      
      std::cout<<"result:"<<resp._result<<" "<<"code:"<<resp._code<<std::endl;
    } 

   //  sock.Close();
    return 0;
 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/945058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UDP 多播(组播)

前言&#xff08;了解分类的IP地址&#xff09; 1.组播&#xff08;多播&#xff09; 单播地址标识单个IP接口&#xff0c;广播地址标识某个子网的所有IP接口&#xff0c;多播地址标识一组IP接口。单播和广播是寻址方案的两个极端&#xff08;要么单个要么全部&#xff09;&am…

商城系统以拼团、砍价、分销为场景的用户增长,裂变和转化才是关键

其实&#xff0c;用户增长是一个非常大的话题。 用户增长不再是传统互联网意义上&#xff0c;一味地追求用户数量上的增长。用户增长是不断的提出增长假设&#xff0c;通过做实验与数据分析&#xff0c;验证假设的正确性&#xff0c;以此循环往复&#xff0c;反复迭代&#xf…

使用智能电磁流量计的时候有哪些方面要注意的?

在各大工业行业领域&#xff0c;流量计量的技术含量和多元性非常高。科学研究它们对提升产品质量、减少企业经营成本、环保节能和处理生态环境保护具有十分重要的意义。智能电磁流量计具备无摩擦阻力、没压力的优势&#xff0c;充足降低了管道里的摩擦阻力&#xff0c;合乎节能…

【微服务部署】02-配置管理

文章目录 1.ConfigMap1.1 创建ConfigMap方式1.2 使用ConfigMap的方式1.3 ConfigMap使用要点建议 2 分布式配置中心解决方案2.1 什么时候选择配置中心2.2 Apollo配置中心系统的能力2.2.1 Apollo创建配置项目2.2.2 项目使用2.2.3 K8s中使用Apollo 1.ConfigMap ConfigMap是K8s提供…

Yolov8-pose关键点检测:模型轻量化创新 | DCNV3结合c2f | CVPR2023

💡💡💡本文解决什么问题:模型轻量化创新引入DCNV3 DCNV3| GFLOPs从9.6降低至8.6,参数量从6482kb降低至5970kb, mAP50从0.921提升至0.926 Yolov8-Pose关键点检测专栏介绍:https://blog.csdn.net/m0_63774211/category_12398833.html ✨✨✨手把手教你从数据标记到…

Java 集合框架1

一、集合框架 1.概念 二、Collection接口 Collection接口之下有两个子接口:List接口/Set接口 List接口是用来处理有序的单列数据&#xff0c;可以有重复的元素。 Set接口是用来处理无序的单列数据&#xff0c;没有重复的元素,重复的元素算一个 三、List接口 …

Redis 7 第三讲 数据类型 进阶篇

⑥ *位图 bitmap 1. 理论 由0和1 状态表现的二进制位的bit 数组。 说明:用String 类型作为底层数据结构实现的一种统计二值状态的数据类型 位图本质是数组,它是基于String 数据类型的按位操作。该数组由多个二进制位组成,每个二进制位都对应一个偏…

DC/DC开关电源学习笔记(一)开关电源技术概述

&#xff08;一&#xff09;开关电源技术概述 1.什么是开关电源&#xff1f;2.开关电源技术概述2.1 小型化、薄型化、轻量化、高频化2.2 高可靠性2.3 低噪声2.4 采用计算机辅助设计和控制 1.什么是开关电源&#xff1f; 开关模式电源&#xff08;Switch Mode Power Supply&…

[JAVA学习笔记]常用类

String类&#xff1a; 一、存放位置&#xff1a; 字符串对象创建好后不能修改 String是引用数据类型&#xff0c;但是这里作为方法参数传递的时候&#xff0c;效果跟基本数据类型是一样的。也就是说在堆中创建出来的字符串”monkey”是不能被改变的&#xff0c;如果…

hadoop 学习:mapreduce 入门案例一:WordCount 统计一个文本中单词的个数

一 需求 这个案例的需求很简单 现在这里有一个文本wordcount.txt&#xff0c;内容如下 现要求你使用 mapreduce 框架统计每个单词的出现个数 这样一个案例虽然简单但可以让新学习大数据的同学熟悉 mapreduce 框架 二 准备工作 &#xff08;1&#xff09;创建一个 maven 工…

Node爬虫项目精简版 wallhaven网站实操 2023.8.29

练习地址&#xff1a; https://wallhaven.cc/toplist const express require(express); const axios require(axios); const cheerio require(cheerio); const schedule require(node-schedule); const fs require(fs);async function downloadImage(url) {const response…

全国工业和信息化应用人才考试-- 服务外包 软件测试复习整理

试卷 201服务外包软件测试考试样卷(1)【附答案】 单项选择题&#xff08;每题1分&#xff0c;共40题40分&#xff09; 多项选择题&#xff08;每题2分&#xff0c;共10题20分&#xff09; 判断题&#xff08;每题1分&#xff0c;共10题10分&#xff09; 填空题&#xff…

基于stm32的ADS1292R 心电波形采集

一、前言 ADS1292R是TI公司早在几年前出产的一款医用级ADC芯片&#xff0c;它主要应用在医疗仪器(心电图ECG),可以监护患者以及病人护理和健身监视器。ADS1292R集成了心电采集所需要的部件&#xff0c;方便设备小型化。它的功耗极低&#xff0c;使得可以作为长时间监控成为可能…

CausalEGM安装使用

1代码来源 github&#xff1a;https://github.com/SUwonglab/CausalEGM/tree/main/src pip&#xff1a;Tutorial for Python Users — CausalEGM documentation 安装&#xff1a;Installation — CausalEGM documentation 版本&#xff1a; 2原理 关于CausalEGM 根据观察…

[文本挖掘和知识发现] 01.红楼梦主题演化分析——文献可视化分析软件CiteSpace入门

八月太忙&#xff0c;还是写一篇吧&#xff01; 本文是作者2023年8月底新开的专栏——《文本挖掘和知识发现》&#xff0c;主要结合Python、大数据分析和人工智能分享文本挖掘、知识图谱、知识发现、图书情报等内容。此外&#xff0c;这些内容也是作者《文本挖掘和知识发现&…

vector实现遇到的问题

前言&#xff1a;vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且它…

【C/C++】课程设计:通讯录管理系统源码,C语言链表实现

大家好呀&#xff0c;亲爱的小伙伴们&#xff01;你们今天有在编写代码吗&#xff1f; 如果有熟悉的小伙伴看到我&#xff0c;就会知道又到了学习源码项目的好时机了&#xff01;没错&#xff0c;今天要分享的同样是一个经典的管理系统项目&#xff1a;通信录管理系统&#xf…

小白学Linux都能学会

文章目录 1. 初识Linux1.1 操作系统1.2 Linux发展历程1.3 Linux简介1.3.1 什么是 Linux1.3.2 Linux的特点 1.4 Linux和Unix区别1.5 Linux和Windows区别1.6 Linux发行商和常见发行版1.7 Linux 应用领域**1.8 Linux之CentOS**1.9 总结 2. 系统与设置命令2.1 学习命令的原因2.2 Li…

7天GMV达220万美元!TikTok Shop爆品榜出炉。

7天GMV达220万美元&#xff01;TikTok Shop爆品榜出炉 8月28日消息&#xff0c;据跨境指南联合TikTok数据分析平台EchoTik发布的数据&#xff0c;监测了上周TikTok Shop印尼、马来西亚、泰国、美国市场GMV前10的商品。上周在印尼市场GMV排名前10的商品中&#xff1a;FREE ONGK…

Kotlin协程flow缓冲buffer

Kotlin协程flow缓冲buffer 先看一个普通的flow&#xff1a; import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking import kotlin.system.measureTimeMillisfun main(args: Array<String>) {val delayTime 100Lru…