目录
协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些 "结构化的数据" 怎么办呢?我们就需要对结构化的数据进行打包,然后通过网络发生至对端主机,再进行解包,进而对端主机接收到结构化的数据;我们将打包数据和解包数据的过程称为 序列化 和 反序列化 的过程。
序列化:将结构化的数据转换为字节序列(可以理解为是长的‘字符串’)发生到网络;
反序列化:将网络中的字节序列(可以理解为是长的‘字符串’)转化为结构化的数据;
我们可以想象一下,张三在中午的某一时刻想要给李四发一个消息:“吃饭了吗”;本质上是肯定包含上图结构化数据中的内容的;一般来说讲这种结构化的数据发送给李四,是需要经过特殊处理的,那就是序列化,转变为一个长字符串;通过网络传输之后,再次经过反序列化的过程,填入李四的结构化数据相应的位置。至此李四就收到了张三发来的信息;
序列化与反序列化有何好处:
- 结构化的数据不便于网络传输;
- 序列化为了应用层网络通信的方便;
- 反序列化为了方便上层使用数据;
- 序列化和反序列化本质就是将应用和网络进行了解耦;
我们通过网络版的计算器这个案例来感受一下序列化与反序列化的过程。这里我们介绍一个序列和反序列化的组件:jsoncpp,它是一个第三方库,需要我们通过yum安装:yum install jsoncpp-devel
- #include
- #include
- #include
-
- using namespace std;
-
- typedef struct request
- {
- int x;
- int y;
- char op;
- }request_t;
-
- int main()
- {
- // 序列化的过程
- request_t req = {10, 20, '*'};
- Json::Value root;// 这个对象可以承装任何对象,json是一种kv式序列化的方案
- root["datax"] = req.x;
- root["datay"] = req.y;
- root["operator"] = req.op;
-
- // FastWriter 和 StyledWriter
- //Json::StyledWriter writer; //方法1
- Json::FastWriter writer; //方法2
- string json_string = writer.write(root); //将结构化的数据转换为字符串形式
- cout << endl << json_string << endl;
-
-
- // 反序列化的过程
-
- string json_string = R"({"datax":10, "datay":20, "operator":42})";//原始字符串
- Json::Reader reader;
- Json::Value root;
-
- reader.parse(json_string, root);
- request_t req;
- req.x = root["datax"].asInt();
- req.y = root["datay"].asInt();
- req.op = (char)root["operator"].asInt();
-
- cout << req.x << req.op << req.y << endl;
- return 0;
- }
定义协议的过程,就是定制结构化数据的过程;这是我们自己定义的协议,client && server 都必须遵守! 这就叫做自定义协议。
- #pragma once
-
- #include
- #include
- #include
- using namespace std;
-
- // 请求格式
- typedef struct request
- {
- int x;
- int y;
- char op;//支持"+-*/%"
- }request_t;
-
- // 响应格式
- typedef struct response
- {
- int code; //表示server运算完毕的计算状态:code(0:success),code(-1:除0了)
- int result; //表示计算结果,能否区分是正常计算结果,还是异常的退出结果
- }response_t;
-
- // 加入序列化与反序列化
-
- // 序列化 :将request_t 序列化为 string
- string SerializeRequest(const request_t& req)
- {
- Json::Value root;// 这个对象可以承装任何对象,json是一种kv式序列化的方案
- root["datax"] = req.x;
- root["datay"] = req.y;
- root["operator"] = req.op;
-
- // FastWriter 和 StyledWriter
- //Json::StyledWriter writer;
- Json::FastWriter writer;
- string json_string = writer.write(root);
- return json_string;
- }
-
- // 反序列化 :将string 反序列化为 request_t
- void DeserializeRequest(const string &json_string, request_t& out)
- {
- Json::Reader reader;
- Json::Value root;
-
- reader.parse(json_string, root);
- out.x = root["datax"].asInt();
- out.y = root["datay"].asInt();
- out.op = (char)root["operator"].asInt();
- }
-
- // 序列化 :将response_t 序列化为 string
- string SerializeResponse(const response_t& resp)
- {
- Json::Value root;// 这个对象可以承装任何对象,json是一种kv式序列化的方案
- root["code"] = resp.code;
- root["result"] = resp.result;
-
- Json::FastWriter writer;
- string res = writer.write(root);
- return res;
- }
-
- // 反序列化 :将string 反序列化为 response_t
- void DeserializeResponse(const string &json_string, response_t& out)
- {
- Json::Reader reader;
- Json::Value root;
-
- reader.parse(json_string, root);
- out.code = root["code"].asInt();
- out.result = root["result"].asInt();
- }
首先我们将套接字的接口函数做一下封装
- #pragma once
-
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- class Sock
- {
- public:
- static int Socket()
- {
- int sock = socket(AF_INET, SOCK_STREAM, 0);
- if(sock < 0)
- {
- cerr << "socket error" << endl;
- exit(2);
- }
- return sock;
- }
-
- static void Bind(int sock, uint16_t port)
- {
- struct sockaddr_in local;
- memset(&local, 0, sizeof(local));
- local.sin_family = AF_INET;
- local.sin_port = htons(port);
- local.sin_addr.s_addr = INADDR_ANY;
-
- if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
- {
- cerr << "bind error!" << endl;
- exit(3);
- }
- }
-
- static void Listen(int sock)
- {
- if(listen(sock, 5) < 0)
- {
- cerr << "listen error!" << endl;
- exit(4);
- }
- }
-
- static int Accept(int sock)
- {
- struct sockaddr_in peer;
- socklen_t len = sizeof(peer);
- int fd = accept(sock, (struct sockaddr*)&peer, &len);
- if(fd >= 0)
- {
- return fd;
- }
- return -1;
- }
-
- static void Connect(int sock, string ip, uint16_t port)
- {
- struct sockaddr_in server;
- memset(&server, 0, sizeof(server));
- server.sin_family = AF_INET;
- server.sin_port = htons(port);
- server.sin_addr.s_addr = inet_addr(ip.c_str());
-
- if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0)
- {
- cout << "Connect Success!" << endl;
- }
- else
- {
- cout << "Connect failed!" << endl;
- exit(5);
- }
- }
-
- };
编写如下的代码:
- #include "protocol.hpp"
- #include "sock.hpp"
-
- void Usage(string proc)
- {
- cout << "Usage: " << proc << " server_ip server_port" << endl;
- }
-
- // ./cal_client server_ip server_port
- int main(int argc, char* argv[])
- {
- if(argc != 3)
- {
- Usage(argv[0]);
- exit(1);
- }
-
- while(true)
- {
- int sock = Sock::Socket();
- Sock::Connect(sock, argv[1], atoi(argv[2]));
-
- // 业务逻辑
- request_t req;
- memset(&req, 0, sizeof(req));
- cout << "Please Enter Data One:";
- cin >> req.x;
- cout << "Please Enter Data Tow:";
- cin >> req.y;
- cout << "Please Enter operator:";
- cin >> req.op;
-
- string json_string = SerializeRequest(req);
- ssize_t s = write(sock, json_string.c_str(), json_string.size());
-
- char buffer[1024];
- s = read(sock, buffer, sizeof(buffer) - 1);
- if(s > 0)
- {
- response_t resp;
- buffer[s] = 0;
- string str = buffer;
- DeserializeResponse(str, resp);
- cout << "code[0: success]: " << resp.code << endl;
- cout << "result: " << resp.result << endl;
- }
-
- }
- return 0;
- }
- #include "protocol.hpp"
- #include "sock.hpp"
- #include
-
- static void Usage(string proc)
- {
- cout << "Usage: " << proc << " port" << endl;
- exit(1);
- }
-
- void* HandlerRequest(void* args)
- {
- int sock = *(int*)args;
- delete (int*)args;
- pthread_detach(pthread_self());
-
- // 发起request -> 分析出了 -> 构建response -> sent(response) -> close(sock)
- while(true)
- {
- // 1.读取请求
- char buffer[1024];
- request_t req;
- ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
- if(s > 0)
- {
- buffer[s] = 0;
- cout << "get a new request: " << buffer << endl;
- string str = buffer;
- DeserializeRequest(str, req);
-
- //request_t req;
- //ssize_t s = read(sock, &req, sizeof(req));
-
- // 2.分析请求 && 3.计算结果
- response_t 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; //代表除0
- else
- resp.result = req.x / req.y;
- break;
-
- case '%':
- if(req.y == 0)
- resp.code = -2; //代表模0
- else
- resp.result = req.x % req.y;
- break;
-
- default:
- resp.code = -3; //代表请求方法异常
- break;
-
- }
- // 4.构建响应,并进行返回
- cout << "request: " << req.x << req.op << req.y << endl;
- //write(sock, &resp, sizeof(resp));
- string send_string = SerializeResponse(resp);//序列化之后的字符串
- write(sock, send_string.c_str(), send_string.size());
-
- cout << "服务结束" << send_string << endl;
- }
- // 5.关闭链接
- close(sock);
- }
- return 0;
- }
-
- int main(int argc, char* argv[])
- {
- if(argc != 2)
- {
- Usage(argv[0]);
- }
-
- uint16_t port = atoi(argv[1]);
- int listen_sock = Sock::Socket();
- Sock::Bind(listen_sock, port);
- Sock::Listen(listen_sock);
-
- for( ; ; )
- {
- int sock = Sock::Accept(listen_sock);
- if(sock >= 0)
- {
- cout << "get a new link..." << endl;
- int* pram = new int(sock);
- pthread_t tid;
- pthread_create(&tid, nullptr, HandlerRequest, pram);
-
- }
- }
-
- return 0;
- }
虽然我们说, 应用层协议是我们程序猿自己定的。但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用。HTTP(超文本传输协议)就是其中之一。