ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【计网】从零开始掌握序列化 --- JSON实现协议 + 设计 传输\会话\应用 三层 [打印本页]

作者: 圆咕噜咕噜    时间: 2024-9-24 06:32
标题: 【计网】从零开始掌握序列化 --- JSON实现协议 + 设计 传输\会话\应用 三层

   唯有空想才配让你不安,      唯有行动才能解除你的不安。        --- 卢思浩 ---       

  
1 知识回首

上一篇文章我们讲解了协议的本质是两边能够看到的结构化数据。并通过传输层的底层明白了为什么read系列函数时全双工支持同时读写的:TCP传输层有两个缓冲区,分别吸取和发送。最重要的是我们将TCP通信的代码举行的重构:
接下来我们要实现是如许的一个结构:

通信过程整体分为三层
如许是一个非常非常优雅的封装使用!!!
2 序列化与编写协议

2.1 使用Json举行序列化

协议是IO的底子,只有协议确定下来,才可以举行通信。
我们这里想要实现一个网络计算器的应用,所以协议分为了两个类:Request和Response。分别作为传入的数据和传出的数据:
他们是作为结构化的数据举行传输,那么想要举行传输就来到了最重要的部分序列化与反序列化!序列化与反序列化可以使用第三方库也可以自己举行编写。这里我们先使用第三方的Json库举行实现:
   Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。 它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。 Jsoncpp 是开源的, 广泛用于各种必要处理 JSON 数据的 C++ 项目中:
    在Linux中使用必要举行安装对应的JSON库:
  1. ubuntu:sudo apt-get install libjsoncpp-dev
  2. Centos: sudo yum install jsoncpp-devel
复制代码
安装之后就可以进使用用了:
   使用起来是非常方便的:
  
  1. Json::StyledWriter writer;
  2. std::string s = writer.write(root)
复制代码
  1. bool parsingSuccessful = reader.parse(json_string,root);
  2. // 访问 JSON 数据
  3. std::string name = root["name"].asString();
  4. int age = root["age"].asInt(); std::string city =
  5. root["city"].asString();
复制代码
通过如许就就可以简洁的完成序列化与反序列化的工作!
2.2 编写协议

根据我们的需求在参加Json使用我们就可以把协议写出来,代码虽然很长但是很好明白:
  1. #pragma once
  2. #include <jsoncpp/json/json.h>
  3. #include <string>
  4. // 协议就是双方都认识的结构化数据
  5. // "len"\r\n"{json}"\r\n
  6. const std::string sep = "\r\n";
  7. struct Request
  8. {
  9. public:
  10.     Request() {}
  11.     Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
  12.     {
  13.     }
  14.     ~Request()
  15.     {
  16.     }
  17.     bool Serialize(std::string *out)
  18.     {
  19.         // 使用现成的 Json 库
  20.         Json::Value root;
  21.         root["x"] = _x;
  22.         root["y"] = _y;
  23.         root["oper"] = _oper;
  24.         Json::FastWriter writer;
  25.         std::string s = writer.write(root);
  26.         *out = s;
  27.         return true;
  28.     }
  29.     bool Deserialize(std::string &in)
  30.     {
  31.         Json::Value root;    // 创建json对象
  32.         Json::Reader reader; // 读取
  33.         bool res = reader.parse(in, root);
  34.         if (res == false)
  35.             return false;
  36.         _x = root["x"].asInt();
  37.         _y = root["y"].asInt();
  38.         _oper = root["oper"].asInt();
  39.         return true;
  40.     }
  41.     int X() { return _x; }
  42.     int Y() { return _y; }
  43.     char Oper() { return _oper; }
  44. private:
  45.     int _x;
  46.     int _y;
  47.     char _oper;
  48. };
  49. struct Response
  50. {
  51.     Response() {}
  52.     Response(int res, int code, std::string desc) : _res(res), _code(code), _desc(desc)
  53.     {
  54.     }
  55.     ~Response()
  56.     {
  57.     }
  58.     bool Serialize(std::string *out)
  59.     {
  60.         // 使用现成的 Json 库
  61.         Json::Value root;
  62.         root["res"] = _res;
  63.         root["code"] = _code;
  64.         root["desc"] = _desc;
  65.         Json::FastWriter writer;
  66.         std::string s = writer.write(root);
  67.         *out = s;
  68.         return true;
  69.     }
  70.     bool Deserialize(std::string &in)
  71.     {
  72.         Json::Value root;    // 创建json对象
  73.         Json::Reader reader; // 读取
  74.         bool res = reader.parse(in, root);
  75.         if (res == false)
  76.             return false;
  77.         _res = root["res"].asInt();
  78.         _code = root["code"].asInt();
  79.         _desc = root["desc"].asInt();
  80.         return true;
  81.     }
  82.     int _res;
  83.     int _code; // 退出码 0:success 1:div zero 2:非法操作
  84.     std::string _desc;
  85. };
复制代码
看一下结果:

完成了底子的序列化和反序列化之后,我们就可以做到从sockfd流中读取数据了吗??不可以!由于不知道Json字符串的长度,就不知道应该读取多少字节!如允许就做不到正确的从数据中获取json字符串!
所以我们还有做一步特殊处理:

  1. // "len"\r\n"{json}"\r\n
  2. const std::string sep = "\r\n";
  3. // 加入报头
  4. std::string Encode(const std::string &jsonstr)
  5. {
  6.     int len = jsonstr.size();
  7.     std::string lenstr = std::to_string(len);
  8.     return lenstr + sep + jsonstr + sep;
  9. }
  10. std::string Decode(std::string &packagestream)
  11. {
  12.     auto pos = packagestream.find(sep);
  13.     if (pos == std::string::npos)
  14.         return std::string();
  15.     // 获取到len
  16.     std::string lenstr = packagestream.substr(0, pos);
  17.     int len = std::stoi(lenstr);
  18.     //算上报头的完整长度!
  19.     int total = lenstr.size() + len + 2 * sep.size();
  20.     if (total > packagestream.size())
  21.         return std::string();
  22.     // 到这里说明可以读取完整数据
  23.     std::string jsonstr = packagestream.substr(pos + sep.size(), len);
  24.     packagestream.erase(total);
  25.     return jsonstr;
  26. }
复制代码
颠末如许的使用,可以包管:

3 封装IOService

将来我们的线程会实行将会实行这个回调函数方法,如今我们不再必要TcpServer来举行IO使用,TcpServer只负责举行获取链接,获取到连接后通过ThreadData结构体将数据传到线程中的回调函数中:
  1.         class ThreadData
  2.     {
  3.     public:
  4.         SockSPtr _sockfd;
  5.         InetAddr _addr;
  6.         TcpServer *_this;
  7.     public:
  8.         ThreadData(SockSPtr sockfd, InetAddr addr, TcpServer *p) : _sockfd(sockfd),
  9.                                                                    _this(p),
  10.                                                                    _addr(addr)
  11.         {
  12.         }
  13.     };
复制代码
在回调函数Execute中:
  1. // 注意设置为静态函数 , 不然参数默认会有TcpServer* this!!!
  2.     static void *Execute(void *args)
  3.     {
  4.         pthread_detach(pthread_self()); // 线程分离!!!
  5.         // 执行Service函数
  6.         TcpServer::ThreadData *td = static_cast<TcpServer::ThreadData *>(args);
  7.         td->_this->_service(td->_sockfd, td->_addr);
  8.         td->_sockfd->Close();
  9.         delete td;
  10.         return nullptr;
  11.     }
复制代码
就可以解析出来套接字文件描述符和客户端信息了!解析出信息之后就去实行会话层的回调函数举行IO使用:
  1. class Service
  2. {
  3. public:
  4.     Service(process_t process) : _process(process)
  5.     {
  6.     }
  7.     void IOExecute(SockSPtr sock, InetAddr &addr)
  8.     {
  9.         LOG(INFO, "service start!!!\n");
  10.         std::string message;
  11.         while (true)
  12.         {
  13.             // 1. 进行读取
  14.             ssize_t n = sock->Recv(&message);
  15.             if (n < 0)
  16.             {
  17.                 LOG(ERROR, "read error: %s\n", addr.AddrStr().c_str());
  18.                 break;
  19.             }
  20.             // 此时获取到客户端发送的数据
  21.             // 但是不能保证是否是完整的报文
  22.             // 2.报文解析
  23.             std::string str = Decode(message); // 通过去报头获取报文
  24.             if (str.empty())
  25.                 continue; // 说明没有完整的报文!
  26.             // 到这里说明有完整的报文!!!
  27.             auto req = Factory::BuildRequestDefault();
  28.             // 3.反序列化初始化Request
  29.             req->Deserialize(str);
  30.             auto res = Factory::BuildResponseDefault();
  31.             // 4.业务处理
  32.             res = _process(req);
  33.             // 5.进行序列化处理
  34.             std::string ret;
  35.             res->Serialize(&ret);
  36.             // 6.加入报头
  37.             Encode(ret);
  38.             // 7.将获取的数据发送回去
  39.             sock->Send(ret);
  40.         }
  41.     }
  42.     ~Service()
  43.     {
  44.     }
  45. private:
  46.     process_t _process;
  47. };
复制代码
4 应用层 — 网络计算器

应用层根据详细必要可以随时改变,我这里以网络计算器为例子举行誊写:
  1. #include "Protocol.hpp"
  2. class NetCal
  3. {
  4. public:
  5.     NetCal() {}
  6.     std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req)
  7.     {
  8.         std::shared_ptr<Response> res = Factory::BuildResponseDefault();
  9.         switch (req->Oper())
  10.         {
  11.         case '+':
  12.             res->_res = req->X() + req->Y();
  13.             res->_code = 0;
  14.             res->_desc = "success";
  15.             break;
  16.         case '-':
  17.             res->_res = req->X() - req->Y();
  18.             res->_code = 0;
  19.             res->_desc = "success";
  20.             break;
  21.         case '*':
  22.             res->_res = req->X() * req->Y();
  23.             res->_code = 0;
  24.             res->_desc = "success";
  25.             break;
  26.         case '/':
  27.         {
  28.             if (req->Y() == 0)
  29.             {
  30.                 res->_code = 1;
  31.                 res->_desc = "div zero";
  32.             }
  33.             res->_res = req->X() / req->Y();
  34.             res->_code = 0;
  35.             res->_desc = "success";
  36.         }
  37.         break;
  38.         case '%':
  39.         {
  40.             if (req->Y() == 0)
  41.             {
  42.                 res->_code = 1;
  43.                 res->_desc = "mod zero";
  44.             }
  45.             res->_res = req->X() % req->Y();
  46.             res->_code = 0;
  47.             res->_desc = "success";
  48.         }
  49.         break;
  50.         default:
  51.             res->_code = 2;
  52.             res->_desc = "illegal operations";
  53.             break;
  54.         }
  55.         return res;
  56.     }
  57.     ~NetCal() {}
  58. };
复制代码
逻辑很简朴不在多加赘述!
5 总结

如今我们的程序分为了三层结构:

我们做到了最大程度的解耦!

如许的结构逻辑非常清晰,而且解耦的非常优雅,值得反复咀嚼!!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4