• 网络版本计算器(再谈“协议“)


                            

            终于考完学校的课程了,大概有1个多月没有再深入学习编程了,博主堕落了堕落哩。这次博主真的要发力嘞,大家一起学习,即使在今年大背景不好的情况下也能逆流而上,拿到好offer。

    目录

    再谈"协议"

    json工具

    json工具安装

    json序列化演示 

    json反序列化演示

    网络版本计算器

    CalClient.cc

    CalServer.cc

    Protocol.hpp

    Sock.hpp

    Makefile

    总结 


    再谈"协议"

          协议是一种 "约定",socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的。如果我们要传输一些"结构化的数据" 怎么办呢?

            网络中传输数据时,是以字符串的形式来发送接收,如果我们客户端发送的数据是一个结构化的数据,那就需要先将数据"序列化"成字符串,服务端接收到数据时,根据协议把序列化的数据转换成结构化的数据。

            是不是感觉上面一段话还是很不理解?没关系,我们来设计一个场景来帮助大家理解,最后我们再来谈一遍这个"协议"。我们谈之前,先来学习一个工具json。

    json工具

    json工具安装

    [cyq@VM-0-7-centos test_json]$ sudo yum install jsoncpp-devel
    

    json序列化演示 

    1. #include
    2. #include
    3. using namespace std;
    4. typedef struct request
    5. {
    6. int x;
    7. int y;
    8. char op;
    9. }request_t;
    10. int main()
    11. {
    12. //序列化的过程
    13. request_t req = {10, 20, '+'};
    14. Json::Value root; //可以承装任何对象,json是一个kv式的序列化方案
    15. root["datax"] = req.x;
    16. root["datay"] = req.y;
    17. root["operator"] = req.op;
    18. //FastWriter
    19. //Json::FastWriter writer;
    20. //StyledWriter
    21. Json::StyledWriter writer;
    22. string json_string = writer.write(root); //kv转换成字符串
    23. cout << json_string << endl;
    24. return 0;
    25. }

        Json::Value root; //可以承装任何对象,json是一个kv式的序列化方案

        root["datax"] = req.x;

        root["datay"] = req.y;

        root["operator"] = req.op; //这几步骤就相当于把req中结构化的数据放入类似于map/哈希表的root容器里面。

      下面是两个不同的字符串序列化风格,我们分别来打印一下就知道风格的区别了。

      //FastWriter

      Json::FastWriter writer; 

       

      //StyledWriter

      Json::StyledWriter writer;

         

            总结:通过上面的两种输出结果就知道了FastWriter和StyledWriter的区别了。实际使用中,我们在写代码的时候会优先使用StyledWriter,因为它方便调试,等到项目上线后,再改为FastWriter。

    string json_string = writer.write(root); //kv转换成字符串,这里write的返回值就是序列化好后的字符串。

    json反序列化演示

    1. int main()
    2. {
    3. //反序列化的过程
    4. string json_string = R"({"datax":10,"datay":20,"operator":43})";
    5. Json::Value root;
    6. Json::Reader reader;
    7. request_t req;
    8. reader.parse(json_string, root);
    9. req.x = root["datax"].asInt();
    10. req.y = root["datay"].asInt();
    11. req.op = (char)root["operator"].asInt();
    12. cout << req.x << " " << req.y << " " << req.op << endl;
    13. return 0;
    14. }

    int main()

    {

        //反序列化的过程

        string json_string = R"({"datax":10,"datay":20,"operator":43})";//json序列化后的字符串

        Json::Value root; //我们反序列化也借助了这个root容器。

        Json::Reader reader;

        request_t req;

        reader.parse(json_string, root); //把序列化的数据读取到容器里面。

        req.x = root["datax"].asInt(); //这时候我们再把容器里面的数据读入到结构体里面。

        req.y = root["datay"].asInt();

        req.op = (char)root["operator"].asInt();

        cout << req.x << " " << req.y << " " << req.op << endl;

        return 0;

    }

    我们来输出一下拿到的数据:

    是不是感觉Josn使用还是比较简单的?我们画个图来进一步理解:

    Makefile

    1. test:test.cc
    2. g++ -o $@ $^ -std=c++11 -ljsoncpp
    3. .PHONY:clean
    4. clean:
    5. rm -f test

    注意:我们在编译的时候,使用了jsoncpp第三方库,所以我们需要加上-ljsoncpp。

    网络版本计算器

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

    约定方案一:
    客户端发送一个形如"1+1"的字符串;
    这个字符串中有两个操作数, 都是整形;
    两个数字之间会有一个字符是运算符, 运算符只能是 + ;
    数字和运算符之间没有空格;
    ...

    约定方案二:
    定义结构体来表示我们需要交互的信息;
    发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
    这个过程叫做 "序列化" 和 "反序列化"

    CalClient.cc

    1. #include "Protocol.hpp"
    2. #include"Sock.hpp"
    3. #include
    4. void Usage(string proc)
    5. {
    6. cout << "Usage: " << proc << " server_ip server_port" << endl;
    7. }
    8. // ./CalClient server_ip server_port
    9. int main(int args, char* argv[])
    10. {
    11. if(args != 3)
    12. {
    13. Usage(argv[0]);
    14. exit(1);
    15. }
    16. int sock = Sock::Socket();
    17. Sock::Connect(sock, argv[1], atoi(argv[2]));
    18. //业务逻辑
    19. request_t req;
    20. memset(&req, 0, sizeof(req));
    21. cout << "Plase Enter datax: ";
    22. cin >> req.x;
    23. cout << "Plase Enter datay: ";
    24. cin >> req.y;
    25. cout << "Plase Enter operator: ";
    26. cin >> req.op;
    27. string json_string = SerializeRequest(req);
    28. ssize_t s = write(sock, json_string.c_str(), json_string.size());
    29. //得到响应
    30. char buffer[1024];
    31. s = read(sock, buffer, sizeof(buffer)-1);
    32. if(s > 0)
    33. {
    34. buffer[s] = 0;
    35. responce_t resp;
    36. string str = buffer; //读到的是序列化的字符串
    37. DeserializerResponce(str, resp);//反序列化
    38. cout << "code[0:success]: " << resp.code << endl;
    39. cout << "request: " << resp.result << endl;
    40. }
    41. return 0;
    42. }

    CalServer.cc

    1. #include
    2. #include "Protocol.hpp"
    3. #include "Sock.hpp"
    4. #include
    5. void Usage(string proc)
    6. {
    7. cout << "Usage: " << proc << " port" << endl;
    8. }
    9. void *HandlerRequest(void *args)
    10. {
    11. int sock = *(int *)args;
    12. delete (int *)args;
    13. pthread_detach(pthread_self());
    14. //读取请求
    15. char buffer[1024];
    16. request_t req;
    17. ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    18. if (s > 0)
    19. {
    20. buffer[s] = 0;
    21. cout << "get a new request ..." << endl;
    22. string str = buffer; //有一个隐式构造函数
    23. DeserializerRequest(str, req); //反序列化
    24. responce_t resp = {0, 0};
    25. switch (req.op)
    26. {
    27. case '+':
    28. resp.result = req.x + req.y;
    29. break;
    30. case '-':
    31. resp.result = req.x - req.y;
    32. break;
    33. case '*':
    34. resp.result = req.x * req.y;
    35. break;
    36. case '/':
    37. if (req.y == 0)
    38. {
    39. resp.code = -1;
    40. }
    41. else
    42. {
    43. resp.result = req.x / req.y;
    44. }
    45. break;
    46. case '%':
    47. if (req.y == 0)
    48. {
    49. resp.code = -2;
    50. }
    51. else
    52. {
    53. resp.result = req.x % req.y;
    54. }
    55. break;
    56. default:
    57. resp.code = -3; //代表请求方法异常
    58. break;
    59. }
    60. cout << "request: " << req.x << req.op << req.y << endl;
    61. string send_string = SerializeResponce(resp);
    62. write(sock, send_string.c_str(), send_string.size());
    63. cout << "服务结束: " << send_string << endl;
    64. }
    65. close(sock);//记住要关闭链接
    66. return nullptr;
    67. }
    68. // ./CalServer port
    69. int main(int args, char *argv[])
    70. {
    71. if (args != 2)
    72. {
    73. Usage(argv[0]);
    74. exit(1);
    75. }
    76. uint16_t port = atoi(argv[1]);
    77. int listen_sock = Sock::Socket();
    78. Sock::Bind(listen_sock, port);
    79. Sock::Listen(listen_sock);
    80. while (1)
    81. {
    82. int sock = Sock::Accept(listen_sock);
    83. if (sock >= 0)
    84. {
    85. //任务处理
    86. cout << "get a new client..." << endl;
    87. int *pram = new int(sock);
    88. pthread_t tid;
    89. pthread_create(&tid, nullptr, HandlerRequest, (void *)pram);
    90. }
    91. }
    92. return 0;
    93. }

    Protocol.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. //请求格式
    7. typedef struct request
    8. {
    9. int x;
    10. int y;
    11. char op;
    12. }request_t;
    13. //响应格式
    14. typedef struct responce
    15. {
    16. int code;
    17. int result;
    18. }responce_t;
    19. //request_t -> string
    20. string SerializeRequest(const request_t& req)
    21. {
    22. //序列化的过程
    23. Json::Value root;
    24. root["datax"] = req.x;
    25. root["datay"] = req.y;
    26. root["operator"] = req.op;
    27. Json::FastWriter writer;
    28. string json_string = writer.write(root);
    29. return json_string;
    30. }
    31. void DeserializerRequest(const string& json_string, request_t& out)
    32. {
    33. //反序列化
    34. Json::Reader reader;
    35. Json::Value root;
    36. reader.parse(json_string, root);
    37. out.x = root["datax"].asInt();
    38. out.y = root["datay"].asInt();
    39. out.op = (char)root["operator"].asInt();
    40. }
    41. string SerializeResponce(const responce_t& resp)
    42. {
    43. Json::Value root;
    44. root["code"] = resp.code;
    45. root["result"] = resp.result;
    46. Json::FastWriter writer;
    47. string json_string = writer.write(root);
    48. return json_string;
    49. }
    50. void DeserializerResponce(const string& json_string, responce_t& out)
    51. {
    52. Json::Value root;
    53. Json::Reader reader;
    54. reader.parse(json_string, root);
    55. out.code = root["code"].asInt();
    56. out.result = root["result"].asInt();
    57. }

    Sock.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. using namespace std;
    11. class Sock
    12. {
    13. public:
    14. static int Socket()
    15. {
    16. int sock = socket(AF_INET, SOCK_STREAM, 0);
    17. if(sock < 0)
    18. {
    19. cerr << "socket err" << endl;
    20. exit(2);
    21. }
    22. return sock;
    23. }
    24. static void Bind(int sock, uint16_t port)
    25. {
    26. struct sockaddr_in local;
    27. memset(&local, 0, sizeof(local));
    28. local.sin_family = AF_INET;
    29. local.sin_port = htons(port);
    30. local.sin_addr.s_addr = INADDR_ANY; //服务端 ip地址
    31. if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    32. {
    33. cerr << "bind error" << endl;
    34. exit(3);
    35. }
    36. }
    37. static void Listen(int sock)
    38. {
    39. if(listen(sock, 5) < 0)
    40. {
    41. cerr << ";isten error" << endl;
    42. exit(4);
    43. }
    44. }
    45. static int Accept(int sock)
    46. {
    47. struct sockaddr_in peer; //输出型参数
    48. socklen_t len = sizeof(peer);
    49. int fd = accept(sock, (struct sockaddr*)&peer, &len);
    50. if(fd >= 0)
    51. {
    52. return fd;
    53. }
    54. return -1;
    55. }
    56. static void Connect(int sock, string ip, uint16_t port)
    57. {
    58. struct sockaddr_in server;
    59. memset(&server, 0, sizeof(server));
    60. server.sin_family = AF_INET;
    61. server.sin_port = htons(port);
    62. server.sin_addr.s_addr = inet_addr(ip.c_str()); //字符串转整型
    63. if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0)
    64. {
    65. cout << "Connect Success" << endl;
    66. }
    67. else
    68. {
    69. cout << "Connect failed" << endl;
    70. exit(5);
    71. }
    72. }
    73. };

    Makefile

    1. .PHONY:all
    2. all:CalClient CalServer
    3. CalClient:CalClient.cc
    4. g++ -o $@ $^ -std=c++11 -ljsoncpp
    5. CalServer:CalServer.cc
    6. g++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp
    7. .PHONY:clean
    8. clean:
    9. rm -f CalClient CalServer

    演示结果:

    总结 

    思考:为什么我们不直接传输结构化的数据?

            我们不能站在上帝视角来看待这个问题,对于主机B,它不知道主机A要发送什么样的结构化的数据,那么它就无法定义响应相应的结构体来存储传过来的数据。这就好像成为了先有鸡还是先有蛋的问题了。 

           如果传输字符串,我们就不必担心这个问题,对于主机B中的接收缓冲区来说,是以字节为单位来读取数据,至于读取字符串出现的问题,还是根据协议来解决,后面的博客博主再来讲解。

    看到这里,给博主点个赞吧~

                            

  • 相关阅读:
    thinkphp6控制器不存在appcontrollerAdmin问题解析及解决
    “平民化”非结构数据处理
    Java注解Annotaton
    python基础语法(一)
    【论文阅读】A high-performance neural prosthesis enabled by control algorithm design
    使用Python实现3D曲线拟合
    Android 开发——环境搭建
    李宏毅深度学习03——神经网络训练不起来怎么办
    在BERT与ElasticSearch进行词向量搜索中遇到的各种坑
    Docker Compose和Consul
  • 原文地址:https://blog.csdn.net/qq_58724706/article/details/127343983