• 应用层协议 —— HTTP(一)


    目录

    一、协议

    二、序列化与反序列化 

    三、网络版的计算器

    1. 约定协议

    2. 计算器客户端

    3. 计算器服务端


    一、协议

             协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些 "结构化的数据" 怎么办呢?我们就需要对结构化的数据进行打包,然后通过网络发生至对端主机,再进行解包,进而对端主机接收到结构化的数据;我们将打包数据和解包数据的过程称为 序列化 反序列化 的过程。

    二、序列化与反序列化 

    序列化:将结构化的数据转换为字节序列(可以理解为是长的‘字符串’)发生到网络;

    反序列化:将网络中的字节序列(可以理解为是长的‘字符串’)转化为结构化的数据; 

            我们可以想象一下,张三在中午的某一时刻想要给李四发一个消息:“吃饭了吗”;本质上是肯定包含上图结构化数据中的内容的;一般来说讲这种结构化的数据发送给李四,是需要经过特殊处理的,那就是序列化,转变为一个长字符串;通过网络传输之后,再次经过反序列化的过程,填入李四的结构化数据相应的位置。至此李四就收到了张三发来的信息;

    序列化与反序列化有何好处:

    • 结构化的数据不便于网络传输;
    • 序列化为了应用层网络通信的方便;
    • 反序列化为了方便上层使用数据;
    • 序列化和反序列化本质就是将应用和网络进行了解耦;

    三、网络版的计算器

            我们通过网络版的计算器这个案例来感受一下序列化与反序列化的过程。这里我们介绍一个序列和反序列化的组件:jsoncpp,它是一个第三方库,需要我们通过yum安装:yum install jsoncpp-devel

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. typedef struct request
    6. {
    7. int x;
    8. int y;
    9. char op;
    10. }request_t;
    11. int main()
    12. {
    13. // 序列化的过程
    14. request_t req = {10, 20, '*'};
    15. Json::Value root;// 这个对象可以承装任何对象,json是一种kv式序列化的方案
    16. root["datax"] = req.x;
    17. root["datay"] = req.y;
    18. root["operator"] = req.op;
    19. // FastWriter 和 StyledWriter
    20. //Json::StyledWriter writer; //方法1
    21. Json::FastWriter writer; //方法2
    22. string json_string = writer.write(root); //将结构化的数据转换为字符串形式
    23. cout << endl << json_string << endl;
    24. // 反序列化的过程
    25. string json_string = R"({"datax":10, "datay":20, "operator":42})";//原始字符串
    26. Json::Reader reader;
    27. Json::Value root;
    28. reader.parse(json_string, root);
    29. request_t req;
    30. req.x = root["datax"].asInt();
    31. req.y = root["datay"].asInt();
    32. req.op = (char)root["operator"].asInt();
    33. cout << req.x << req.op << req.y << endl;
    34. return 0;
    35. }

    1. 约定协议

            定义协议的过程,就是定制结构化数据的过程;这是我们自己定义的协议,client && server 都必须遵守! 这就叫做自定义协议。

    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 response
    15. {
    16. int code; //表示server运算完毕的计算状态:code(0:success),code(-1:除0了)
    17. int result; //表示计算结果,能否区分是正常计算结果,还是异常的退出结果
    18. }response_t;
    19. // 加入序列化与反序列化
    20. // 序列化 :将request_t 序列化为 string
    21. string SerializeRequest(const request_t& req)
    22. {
    23. Json::Value root;// 这个对象可以承装任何对象,json是一种kv式序列化的方案
    24. root["datax"] = req.x;
    25. root["datay"] = req.y;
    26. root["operator"] = req.op;
    27. // FastWriter 和 StyledWriter
    28. //Json::StyledWriter writer;
    29. Json::FastWriter writer;
    30. string json_string = writer.write(root);
    31. return json_string;
    32. }
    33. // 反序列化 :将string 反序列化为 request_t
    34. void DeserializeRequest(const string &json_string, request_t& out)
    35. {
    36. Json::Reader reader;
    37. Json::Value root;
    38. reader.parse(json_string, root);
    39. out.x = root["datax"].asInt();
    40. out.y = root["datay"].asInt();
    41. out.op = (char)root["operator"].asInt();
    42. }
    43. // 序列化 :将response_t 序列化为 string
    44. string SerializeResponse(const response_t& resp)
    45. {
    46. Json::Value root;// 这个对象可以承装任何对象,json是一种kv式序列化的方案
    47. root["code"] = resp.code;
    48. root["result"] = resp.result;
    49. Json::FastWriter writer;
    50. string res = writer.write(root);
    51. return res;
    52. }
    53. // 反序列化 :将string 反序列化为 response_t
    54. void DeserializeResponse(const string &json_string, response_t& out)
    55. {
    56. Json::Reader reader;
    57. Json::Value root;
    58. reader.parse(json_string, root);
    59. out.code = root["code"].asInt();
    60. out.result = root["result"].asInt();
    61. }

    2. 计算器客户端

    首先我们将套接字的接口函数做一下封装 

    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 error" << 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;
    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 << "listen 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. };

     编写如下的代码:

    1. #include "protocol.hpp"
    2. #include "sock.hpp"
    3. void Usage(string proc)
    4. {
    5. cout << "Usage: " << proc << " server_ip server_port" << endl;
    6. }
    7. // ./cal_client server_ip server_port
    8. int main(int argc, char* argv[])
    9. {
    10. if(argc != 3)
    11. {
    12. Usage(argv[0]);
    13. exit(1);
    14. }
    15. while(true)
    16. {
    17. int sock = Sock::Socket();
    18. Sock::Connect(sock, argv[1], atoi(argv[2]));
    19. // 业务逻辑
    20. request_t req;
    21. memset(&req, 0, sizeof(req));
    22. cout << "Please Enter Data One:";
    23. cin >> req.x;
    24. cout << "Please Enter Data Tow:";
    25. cin >> req.y;
    26. cout << "Please Enter operator:";
    27. cin >> req.op;
    28. string json_string = SerializeRequest(req);
    29. ssize_t s = write(sock, json_string.c_str(), json_string.size());
    30. char buffer[1024];
    31. s = read(sock, buffer, sizeof(buffer) - 1);
    32. if(s > 0)
    33. {
    34. response_t resp;
    35. buffer[s] = 0;
    36. string str = buffer;
    37. DeserializeResponse(str, resp);
    38. cout << "code[0: success]: " << resp.code << endl;
    39. cout << "result: " << resp.result << endl;
    40. }
    41. }
    42. return 0;
    43. }

    3. 计算器服务端

    1. #include "protocol.hpp"
    2. #include "sock.hpp"
    3. #include
    4. static void Usage(string proc)
    5. {
    6. cout << "Usage: " << proc << " port" << endl;
    7. exit(1);
    8. }
    9. void* HandlerRequest(void* args)
    10. {
    11. int sock = *(int*)args;
    12. delete (int*)args;
    13. pthread_detach(pthread_self());
    14. // 发起request -> 分析出了 -> 构建response -> sent(response) -> close(sock)
    15. while(true)
    16. {
    17. // 1.读取请求
    18. char buffer[1024];
    19. request_t req;
    20. ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    21. if(s > 0)
    22. {
    23. buffer[s] = 0;
    24. cout << "get a new request: " << buffer << endl;
    25. string str = buffer;
    26. DeserializeRequest(str, req);
    27. //request_t req;
    28. //ssize_t s = read(sock, &req, sizeof(req));
    29. // 2.分析请求 && 3.计算结果
    30. response_t resp = {0, 0};
    31. switch(req.op)
    32. {
    33. case '+':
    34. resp.result = req.x + req.y;
    35. break;
    36. case '-':
    37. resp.result = req.x - req.y;
    38. break;
    39. case '*':
    40. resp.result = req.x * req.y;
    41. break;
    42. case '/':
    43. if(req.y == 0)
    44. resp.code = -1; //代表除0
    45. else
    46. resp.result = req.x / req.y;
    47. break;
    48. case '%':
    49. if(req.y == 0)
    50. resp.code = -2; //代表模0
    51. else
    52. resp.result = req.x % req.y;
    53. break;
    54. default:
    55. resp.code = -3; //代表请求方法异常
    56. break;
    57. }
    58. // 4.构建响应,并进行返回
    59. cout << "request: " << req.x << req.op << req.y << endl;
    60. //write(sock, &resp, sizeof(resp));
    61. string send_string = SerializeResponse(resp);//序列化之后的字符串
    62. write(sock, send_string.c_str(), send_string.size());
    63. cout << "服务结束" << send_string << endl;
    64. }
    65. // 5.关闭链接
    66. close(sock);
    67. }
    68. return 0;
    69. }
    70. int main(int argc, char* argv[])
    71. {
    72. if(argc != 2)
    73. {
    74. Usage(argv[0]);
    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. for( ; ; )
    81. {
    82. int sock = Sock::Accept(listen_sock);
    83. if(sock >= 0)
    84. {
    85. cout << "get a new link..." << endl;
    86. int* pram = new int(sock);
    87. pthread_t tid;
    88. pthread_create(&tid, nullptr, HandlerRequest, pram);
    89. }
    90. }
    91. return 0;
    92. }

            虽然我们说, 应用层协议是我们程序猿自己定的。但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用。HTTP(超文本传输协议)就是其中之一。
  • 相关阅读:
    Linux线程1
    CentOS上升级glibc2.17至glibc2.31
    数字图标的使用(阁瑞钛伦特软件-九耶实训)
    Alook获取站点cookie详细教程
    消息队列 RabbitMQ 遇上可观测--业务链路可视化
    百位上的数字(蓝桥杯真题)
    Java实现一个简单的GitHub仓库信息爬取
    计算机毕业设计 基于协同推荐的白酒销售管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
    Linux系统调试篇——GDBSERVER远程调试
    “2022锦江行”,维也纳国际酒店、丽柏酒店惊艳同台,中高端酒店再出标杆示范
  • 原文地址:https://blog.csdn.net/sjsjnsjnn/article/details/127102673