
终于考完学校的课程了,大概有1个多月没有再深入学习编程了,博主堕落了堕落哩。这次博主真的要发力嘞,大家一起学习,即使在今年大背景不好的情况下也能逆流而上,拿到好offer。
目录
协议是一种 "约定",socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的。如果我们要传输一些"结构化的数据" 怎么办呢?
网络中传输数据时,是以字符串的形式来发送接收,如果我们客户端发送的数据是一个结构化的数据,那就需要先将数据"序列化"成字符串,服务端接收到数据时,根据协议把序列化的数据转换成结构化的数据。
是不是感觉上面一段话还是很不理解?没关系,我们来设计一个场景来帮助大家理解,最后我们再来谈一遍这个"协议"。我们谈之前,先来学习一个工具json。
[cyq@VM-0-7-centos test_json]$ sudo yum install jsoncpp-devel

- #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
- //Json::FastWriter writer;
-
- //StyledWriter
- Json::StyledWriter writer;
-
- string json_string = writer.write(root); //kv转换成字符串
- cout << json_string << endl;
- return 0;
- }
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的返回值就是序列化好后的字符串。
- int main()
- {
- //反序列化的过程
- string json_string = R"({"datax":10,"datay":20,"operator":43})";
-
- Json::Value 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;
- }
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
- test:test.cc
- g++ -o $@ $^ -std=c++11 -ljsoncpp
- .PHONY:clean
- clean:
- rm -f test
注意:我们在编译的时候,使用了jsoncpp第三方库,所以我们需要加上-ljsoncpp。
例如, 我们需要实现一个服务器版的加法器。我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。
约定方案一:
客户端发送一个形如"1+1"的字符串;
这个字符串中有两个操作数, 都是整形;
两个数字之间会有一个字符是运算符, 运算符只能是 + ;
数字和运算符之间没有空格;
...约定方案二:
定义结构体来表示我们需要交互的信息;
发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
这个过程叫做 "序列化" 和 "反序列化"
- #include "Protocol.hpp"
- #include"Sock.hpp"
- #include
-
-
- void Usage(string proc)
- {
- cout << "Usage: " << proc << " server_ip server_port" << endl;
- }
- // ./CalClient server_ip server_port
- int main(int args, char* argv[])
- {
- if(args != 3)
- {
- Usage(argv[0]);
- exit(1);
- }
- int sock = Sock::Socket();
- Sock::Connect(sock, argv[1], atoi(argv[2]));
-
- //业务逻辑
- request_t req;
- memset(&req, 0, sizeof(req));
- cout << "Plase Enter datax: ";
- cin >> req.x;
- cout << "Plase Enter datay: ";
- cin >> req.y;
- cout << "Plase 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)
- {
- buffer[s] = 0;
- responce_t resp;
- string str = buffer; //读到的是序列化的字符串
- DeserializerResponce(str, resp);//反序列化
- cout << "code[0:success]: " << resp.code << endl;
- cout << "request: " << resp.result << endl;
- }
- return 0;
- }
- #include
- #include "Protocol.hpp"
- #include "Sock.hpp"
- #include
-
- void Usage(string proc)
- {
- cout << "Usage: " << proc << " port" << endl;
- }
-
- void *HandlerRequest(void *args)
- {
- int sock = *(int *)args;
- delete (int *)args;
-
- pthread_detach(pthread_self());
-
- //读取请求
- 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 ..." << endl;
- string str = buffer; //有一个隐式构造函数
-
- DeserializerRequest(str, req); //反序列化
-
- responce_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;
- }
- 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;
- }
-
- cout << "request: " << req.x << req.op << req.y << endl;
- string send_string = SerializeResponce(resp);
- write(sock, send_string.c_str(), send_string.size());
- cout << "服务结束: " << send_string << endl;
- }
- close(sock);//记住要关闭链接
- return nullptr;
- }
-
- // ./CalServer port
- int main(int args, char *argv[])
- {
- if (args != 2)
- {
- Usage(argv[0]);
- exit(1);
- }
- uint16_t port = atoi(argv[1]);
-
- int listen_sock = Sock::Socket();
- Sock::Bind(listen_sock, port);
- Sock::Listen(listen_sock);
-
- while (1)
- {
- int sock = Sock::Accept(listen_sock);
- if (sock >= 0)
- {
- //任务处理
- cout << "get a new client..." << endl;
- int *pram = new int(sock);
- pthread_t tid;
- pthread_create(&tid, nullptr, HandlerRequest, (void *)pram);
- }
- }
- return 0;
- }
- #pragma once
-
- #include
- #include
- #include
-
- using namespace std;
-
- //请求格式
- typedef struct request
- {
- int x;
- int y;
- char op;
- }request_t;
-
- //响应格式
- typedef struct responce
- {
- int code;
- int result;
- }responce_t;
-
- //request_t -> string
- string SerializeRequest(const request_t& req)
- {
- //序列化的过程
- Json::Value root;
- root["datax"] = req.x;
- root["datay"] = req.y;
- root["operator"] = req.op;
-
- Json::FastWriter writer;
- string json_string = writer.write(root);
- return json_string;
- }
-
- void DeserializerRequest(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();
-
- }
-
- string SerializeResponce(const responce_t& resp)
- {
- Json::Value root;
- root["code"] = resp.code;
- root["result"] = resp.result;
-
- Json::FastWriter writer;
- string json_string = writer.write(root);
- return json_string;
- }
-
- void DeserializerResponce(const string& json_string, responce_t& out)
- {
- Json::Value root;
- Json::Reader reader;
-
- 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 err" << 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; //服务端 ip地址
-
- 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 << ";isten 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);
- }
- }
- };
- .PHONY:all
- all:CalClient CalServer
-
- CalClient:CalClient.cc
- g++ -o $@ $^ -std=c++11 -ljsoncpp
- CalServer:CalServer.cc
- g++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp
-
- .PHONY:clean
- clean:
- rm -f CalClient CalServer
演示结果:


思考:为什么我们不直接传输结构化的数据?
我们不能站在上帝视角来看待这个问题,对于主机B,它不知道主机A要发送什么样的结构化的数据,那么它就无法定义响应相应的结构体来存储传过来的数据。这就好像成为了先有鸡还是先有蛋的问题了。
如果传输字符串,我们就不必担心这个问题,对于主机B中的接收缓冲区来说,是以字节为单位来读取数据,至于读取字符串出现的问题,还是根据协议来解决,后面的博客博主再来讲解。
看到这里,给博主点个赞吧~
