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

文章目录

  • 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. 如何处理结构化数据?

【计算机网络】序列化与反序列化_第1张图片

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

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

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


序列化 与 反序列化

【计算机网络】序列化与反序列化_第2张图片

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

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

2. 实现网络版计算器

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

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

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

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


创建套接字——Socket

输入 man socket,创建套接字

【计算机网络】序列化与反序列化_第3张图片

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


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

在这里插入图片描述

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

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

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


【计算机网络】序列化与反序列化_第4张图片

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

绑定——Bind

输入 man 2 bind ,查看绑定

【计算机网络】序列化与反序列化_第5张图片

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

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


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

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


【计算机网络】序列化与反序列化_第6张图片

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


【计算机网络】序列化与反序列化_第7张图片

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

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

【计算机网络】序列化与反序列化_第8张图片

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


【计算机网络】序列化与反序列化_第9张图片

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

获取连接——Accept

输入 man 2 accept

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

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

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


【计算机网络】序列化与反序列化_第10张图片

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


【计算机网络】序列化与反序列化_第11张图片

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


【计算机网络】序列化与反序列化_第12张图片

发起连接——Connect

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

【计算机网络】序列化与反序列化_第13张图片

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

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

初始化

【计算机网络】序列化与反序列化_第14张图片

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

启动

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

【计算机网络】序列化与反序列化_第15张图片

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

多线程的使用

【计算机网络】序列化与反序列化_第16张图片

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


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


创建一个结构体ThreadData内部包含sock套接字以及一个指向服务器的指针 ip地址 port端口号
【计算机网络】序列化与反序列化_第17张图片

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


【计算机网络】序列化与反序列化_第18张图片

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


【计算机网络】序列化与反序列化_第19张图片

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

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

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

【计算机网络】序列化与反序列化_第20张图片

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

Request的自定义序列化

【计算机网络】序列化与反序列化_第21张图片

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


【计算机网络】序列化与反序列化_第22张图片

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


使用 宏, 将SEP表示为空格

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

Request的自定义反序列化

【计算机网络】序列化与反序列化_第23张图片

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


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

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

【计算机网络】序列化与反序列化_第24张图片

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

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

【计算机网络】序列化与反序列化_第25张图片

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

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

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

【计算机网络】序列化与反序列化_第26张图片

toInt——字符串转化为整数

【计算机网络】序列化与反序列化_第27张图片

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


【计算机网络】序列化与反序列化_第28张图片

Response的自定义序列化

【计算机网络】序列化与反序列化_第29张图片

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

将 res_string SEP 和 code_string 连接起来

Response的自定义反序列化

【计算机网络】序列化与反序列化_第30张图片

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

4. Tcpserver.hpp的调用

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

【计算机网络】序列化与反序列化_第31张图片

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


ReadPackage的实现

输入 man recv

【计算机网络】序列化与反序列化_第32张图片

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


【计算机网络】序列化与反序列化_第33张图片

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


【计算机网络】序列化与反序列化_第34张图片

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


【计算机网络】序列化与反序列化_第35张图片

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

2.获取有效载荷部分

RmoveHeader的实现

【计算机网络】序列化与反序列化_第36张图片

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


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

【计算机网络】序列化与反序列化_第37张图片

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

4.提取用户的请求数据

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


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


【计算机网络】序列化与反序列化_第38张图片

将Request处理完 变为 Response


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

【计算机网络】序列化与反序列化_第39张图片


【计算机网络】序列化与反序列化_第40张图片

先将结果与错误码默认都设置为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
#include
#include
#include

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
#include
#include 
#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
#include



Sock.hpp(Tcp套接字)

#pragma once 
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include

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

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
#include
#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;
 }

你可能感兴趣的:(计算机网络,计算机网络,网络)