计算机网络—TCP协议详解:特性、应用(2)

 

                                        🎬慕斯主页修仙—别有洞天

                                       ♈️今日夜电波:マリンブルーの庭園—ずっと真夜中でいいのに。

                                                           0:34━━━━━━️💟──────── 3:34
                                                                🔄   ◀️   ⏸   ▶️    ☰  

                                 💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


 

目录

协议的定制

协议定制的概念

封装套接字

序列化和反序列化

序列化(Serialization)

反序列化(Deserialization)

我们该如何保证数据的完整性?

实现一个自定义协议

根据上面实现的封装实现网络版计算器

封装计算方法

服务器的封装实现

服务器的实现

客户端的实现

json

1. 创建JSON对象

2. 添加成员到JSON对象

3. 创建JSON数组

4. 嵌套JSON对象

5. 生成JSON字符串

注意事项

示例:生成一个完整的JSON对象

1. 包含头文件

2. 解析JSON字符串

3. 访问JSON对象成员

4. 访问JSON数组元素

5. 访问嵌套JSON对象

6. 类型检查和转换

注意事项

示例:接收一个完整的JSON对象

使用json序列化和反序列化的版本

再谈OSI七层模型


协议的定制

协议定制的概念

        协议定制是指在应用程序中实现的特定通信规则。这些协议根据特定需求进行设计,以实现不同的功能和性能。在计算机网络中,协议是通信双方约定好的一种方式,用于数据的发送、读取以及双方之间的数据通信。这种约定的方式就构成了一种协议,它定义了通信双方之间交换的报文的格式、顺序以及报文发送或接收一条报文或其他事件所采取的动作。

        协议定制主要涉及以下几个关键步骤(非常重要!!!)

  1. 封装套接字:这是自定义协议的基础,通过套接字实现数据的传输。
  2. 构建请求与响应:根据应用程序的需求,定义请求和响应的字段,这些字段本身就是协议的一部分。
  3. 序列化和反序列化:发送数据时,将结构体按照一定规则转换成字符串;接收数据时,按照相同的规则将字符串转换回结构体。这个过程确保了数据的完整性和准确性。
  4. 报头添加和去除:根据协议规范,在数据包的特定位置添加或去除报头信息。
  5. 报文读取:从接收缓冲区中读取数据,并根据协议规范解析报文内容。

        自定义协议的优势在于其灵活性、可靠性和高效性。它可以根据应用程序的需求进行设计,实现特定的功能和性能;同时,通过协议的定制,可以确保数据在传输过程中的完整性和安全性;此外,根据应用程序的需求优化协议,还可以提高通信效率。

        在实际应用中,自定义协议广泛用于各种场景,如游戏开发、物联网设备通信以及数据传输等。通过自定义协议,可以实现更高效、更安全的数据通信,满足不同应用场景的需求。

        接下来我们按照上述的步骤一步一步实现协议的定制,通过编写一个网络版计算器来理解:

封装套接字

        前面的文章中我们已经详细的介绍了TCP和UDP常用的套接字,对于他们的封装可以根据自身的需求来,这里结合了之前文章的日志功能来实现:

Socket.hpp

#pragma once#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include "Log.hpp"
using namespace std;const int backlog = 10;enum
{SocketErr = 2,BindErr,ListenErr,
};class Socke
{public:Socke() : _sockfd(-1) {}~Socke() {}public:void Socket(){_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){lg.LogMessage(Fatal, "socker error, %s: %d", strerror(errno), errno);exit(SocketErr);}}void Bind(uint16_t port){struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_port = htons(port);local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;if (bind(_sockfd, (const struct sockaddr *)&local, sizeof(local)) < 0){lg.LogMessage(Fatal, "bind error,%s,%d", strerror(errno), errno);exit(BindErr);}}void Listen(){if (listen(_sockfd, backlog) < 0){lg.LogMessage(Fatal, "listen error,%s: %d", strerror(errno), errno);exit(ListenErr);}}int Accept(string *clientip, uint16_t *clientport){struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len;int newfd = accept(_sockfd, (struct sockaddr *)&peer, &len);if (newfd < 0){lg.LogMessage(Warning, "accept error, %s: %d", strerror(errno), errno);return -1;}char ipstr[64];inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));*clientip = ipstr;*clientport = ntohs(peer.sin_port);return newfd;}bool Connect(const string &ip, const uint16_t &port){struct sockaddr_in peer;bzero(&peer, sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));int n = connect(_sockfd, (const struct sockaddr *)&peer, sizeof(peer));if (n < 0){std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;return false;}return true;}void Close(){close(_sockfd);}int Fd(){return _sockfd;}private:int _sockfd;
};

序列化和反序列化

        在Linux中,序列化和反序列化是数据处理中的两个重要概念,尤其在涉及到进程间通信、网络通信或数据存储时。下面将详细解释这两个概念:

序列化(Serialization)

        序列化是将数据结构或对象状态转换为可以存储或传输的形式的过程。在Linux(以及大多数其他编程环境中),序列化通常意味着将数据结构转换为字节流,这样它就可以被写入文件、通过网络发送,或者作为其他程序或进程可以读取的数据。

        序列化的主要目的是:

  1. 持久化:将对象或数据结构保存到磁盘或其他存储介质中,以便以后恢复。
  2. 传输:通过网络发送对象或数据结构。

        在Linux中,你可以使用多种方法进行序列化,包括但不限于:

  • 使用标准库:例如,C++的boost::serialization
  • 自定义格式:例如,将数据结构转换为JSON、XML或Protocol Buffers等格式。
  • 使用系统调用:如fwrite等用于将数据写入文件。

大致的图解:

反序列化(Deserialization)

        反序列化是序列化的逆过程,即从字节流中恢复数据结构或对象状态。在Linux中,当你从文件或网络中读取数据时,通常需要将这些数据反序列化为原始的数据结构或对象,以便程序可以理解和使用这些数据。

反序列化的主要目的是:

  1. 恢复状态:从持久化存储中读取数据并恢复对象或数据结构的状态。
  2. 接收数据:从网络接收数据并将其转换为程序可以处理的数据结构或对象。

        在Linux中反序列化的方法通常与序列化时使用的方法相对应。例如,如果你使用boost::serialization库进行序列化,那么你也会使用相同的库进行反序列化。同样,如果你将数据保存为JSON格式,你将使用JSON解析器进行反序列化。

大致的图解:

我们该如何保证数据的完整性?

        实际上,前面的文章中提到的write、read像读写文件一样操作网络是不准确的,我们的write实际上是将应用层的数据拷贝到传输层中的缓冲区中,而read则是将传输层的数据拷贝到应用层中!由于TCP和UDP都是全双工的,发送和接收的缓冲区是不会冲突的,因此这两项操作是可以同时进行的!

        但是如果这个时候,我们网络波动或者服务器压力变大等等原因导致服务器读取的速度跟不上客户端发送的速度(根据各种各样的原因)导致我们的缓冲区积攒了大量的保文,或者并没有得到完整的报文,那该怎么办呢?这时,就需要使用协议来解决这样的问题了!我们根据协议来读取数据,比如报头中包含着数据的长度、数据的类型等等,以及我们按照一定的分割符来分隔了报文,只有遵循协议规则才会将指定范围的报文读取!或者将报文舍弃,回复重新发送报文的信息等等操作!

实现一个自定义协议

        我们就按照如上所提到的规则制定一个协议,这个协议包含了序列化和反序列化、对于序列化和反序列化的“大字符串”进行相应的添加报头、加工或者卸下报头、解析等等操作:

        根据上图的报文所示,我们分为请求报文以及回应报文两种类来定义协议。需要注意的是我们这两个类Request、Response需要做的仅仅是提供报文数据,而其中对与长度以及\n的添加和去除都是通过两个共用的函数Encode和Decode来实现的,我们的服务器以及客户端都是共用这样一套协议规则的!!!也就是说我们都清楚如何发送和接收报文!!!

Protocol.hpp

#pragma once#include <iostream>
#include <string>
using namespace std;const string blank_space_sep = " ";
const string protocol_sep = "\n";// "len"\n"x op y"\nXXXXXX
string Encode(string &content)
{string package = to_string(content.size());package += protocol_sep;package += content;package += protocol_sep;return package;
}bool Decode(string &package, string *content)
{size_t pos = package.find(protocol_sep);if (pos == string::npos)return false;string len_str = package.substr(0, pos);size_t len = stoi(len_str);size_t total_len = len + len_str.size() + 2;if (total_len > package.size())return false;*content = package.substr(pos + 1, len);package.erase(0, total_len);return true;
}class Request
{
public:Request(int data1, int data2, char oper) : _x(data1), _y(data2), _op(oper){}Request() {}//  "x op y"bool Serialize(string *out){string str = to_string(_x);str += blank_space_sep;str += _op;str += blank_space_sep;str += to_string(_y);*out = str;return true;}bool Deserialize(const string &in){size_t left = in.find(blank_space_sep);if (left == string::npos)return false;string p_x = in.substr(0, left);size_t right = in.rfind(blank_space_sep);if (right == string::npos)return false;string p_y = in.substr(right + 1);if (left + 2 != right)return false;_op = in[left + 1];_x = stoi(p_x);_y = stoi(p_y);return true;}void DebugPrint(){std::cout << "新请求构建完成:  " << _x << _op << _y << "=?" << std::endl;}public:int _x;int _y;char _op;
};class Response
{
public:Response(int res, int c) : _result(res), _code(c){}Response(){}bool Serialize(string *out){string str = to_string(_result);str += blank_space_sep;str += to_string(_code);*out = str;return true;}// "result code"bool Deserialize(const std::string &in){ssize_t pos = in.find(blank_space_sep);if (pos == string::npos)return false;string p_l = in.substr(0, pos);string p_r = in.substr(pos + 1);_result = stoi(p_l);_code = stoi(p_r);return true;}void DebugPrint(){std::cout << "结果响应完成, result: " << _result << ", code: " << _code << std::endl;}public:int _result;int _code;
};

根据上面实现的封装实现网络版计算器

封装计算方法

        实际上就是处理服务器从网络读取到了“大字符串”后我们先进行报头的去除(这一步也可以认为是验证的数据完整性的过程)、解析出其中的数据(反序列化),将得到的数据进行计算。计算完成后,将数据进行序列化,然后添加报头,再将处理好的结果的“大字符串”推送到网络中,最后由客户端接收。实现如下:

ServerCal.hpp

#pragma once
#include <iostream>
#include "Protocol.hpp"enum
{Div_Zero = 1,Mod_Zero,Other_Oper
};class ServerCal
{public:ServerCal(){}Response CalculatorHelper(const Request &req){Response 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 = Div_Zero;elseresp._result = req._x / req._y;}break;case '%':{if (req._y == 0)resp._code = Mod_Zero;elseresp._result = req._x % req._y;}break;default:resp._code = Other_Oper;break;}return resp;}string Calculator(string &package){string content;bool r = Decode(package, &content);if (!r)return "";Request req;r = req.Deserialize(content); // "10 + 20" ->x=10 op=+ y=20if (!r)return "";Response resp = CalculatorHelper(req); // result=30 code=0;resp.Serialize(&content);  // "30 0"content = Encode(content); // "len"\n"30 0"return content;}~ServerCal(){}
};

服务器的封装实现

        我们实现一个基于TCP协议的多进程服务器的封装:

TcpServer.hpp

#pragma once
#include <functional>
#include <string>
#include <signal.h>
#include "Log.hpp"
#include "Socket.hpp"using func_t = function<string(string &)>;class TcpServer
{
public:TcpServer(uint16_t port, func_t callback): _port(port), _callback(callback){}bool InitServer(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();lg.LogMessage(Info, "init server .... done");return true;}void Start(){signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);while (true){std::string clientip;uint16_t clientport;int sockfd = _listensock.Accept(&clientip, &clientport);if (sockfd < 0)continue;lg.LogMessage(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);// 提供服务if (fork() == 0){_listensock.Close();std::string inbuffer_stream;// 数据计算while (true){char buffer[1280];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;inbuffer_stream += buffer;lg.LogMessage(Debug, "debug:\n%s", inbuffer_stream.c_str());while (true){std::string info = _callback(inbuffer_stream);if (info.empty())break;lg.LogMessage(Debug, "debug, response:\n%s", info.c_str());lg.LogMessage(Debug, "debug:\n%s", inbuffer_stream.c_str());write(sockfd, info.c_str(), info.size());}}else if (n == 0)break;elsebreak;}exit(0);}close(sockfd);}}~TcpServer(){}private:uint16_t _port;Socke _listensock;func_t _callback;
};

服务器的实现
#include "TcpServer.hpp"
#include "ServerCal.hpp"
#include <unistd.h>static void Usage(const std::string &proc)
{std::cout << "\nUsage: " << proc << " port\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]);ServerCal cal;TcpServer *tsvp = new TcpServer(port, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));tsvp->InitServer();tsvp->Start();return 0;
}

客户端的实现
#include <iostream>
#include <string>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include "Socket.hpp"
#include "Protocol.hpp"static void Usage(const std::string &proc)
{std::cout << "\nUsage: " << proc << " serverip serverport\n"<< std::endl;
}// ./clientcal ip port
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);Socke sockfd;sockfd.Socket();bool r = sockfd.Connect(serverip, serverport);if(!r) return 1;srand(time(nullptr) ^ getpid());int cnt = 1;const std::string opers = "+-*/%=-=&^";std::string inbuffer_stream;while(cnt <= 10){std::cout << "===============第" << cnt << "次测试....., " << "===============" << std::endl;int x = rand() % 100 + 1;usleep(1234);int y = rand() % 100;usleep(4321);char oper = opers[rand()%opers.size()];Request req(x, y, oper);req.DebugPrint();std::string package;req.Serialize(&package);package = Encode(package);write(sockfd.Fd(), package.c_str(), package.size());// std::cout << "这是最新的发出去的请求: " << n << "\n" << package;// n = write(sockfd.Fd(), package.c_str(), package.size());// std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package;// n = write(sockfd.Fd(), package.c_str(), package.size());// std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package;// n = write(sockfd.Fd(), package.c_str(), package.size());// std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package;char buffer[128];ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); // 我们也无法保证我们能读到一个完整的报文if(n > 0){buffer[n] = 0;inbuffer_stream += buffer; // "len"\n"result code"\nstd::cout << inbuffer_stream << std::endl;std::string content;bool r = Decode(inbuffer_stream, &content); // "result code"assert(r);Response resp;r = resp.Deserialize(content);assert(r);resp.DebugPrint();}std::cout << "=================================================" << std::endl;sleep(1);cnt++;}sockfd.Close();return 0;
}

json

        在C++中,使用jsoncpp库生成JSON数据主要遵循JSON数据的语法规则,并结合jsoncpp提供的API来构建JSON对象。以下是一些关于使用jsoncpp生成JSON数据的基本规则和示例:

1. 创建JSON对象

        你可以使用Json::Value对象来表示一个JSON实体(对象、数组、字符串、数字、布尔值或null)。

Json::Value root; // 创建一个空的JSON对象

2. 添加成员到JSON对象

        你可以使用operator[]add()方法来向JSON对象添加成员。

root["key"] = "value"; // 添加一个字符串成员
root["number"] = 123;  // 添加一个数字成员
root["boolean"] = true; // 添加一个布尔成员

3. 创建JSON数组

        你可以将Json::Value对象设置为数组类型,并使用append()方法或operator[]来添加元素。

Json::Value array; // 创建一个空的JSON数组
array.append("element1"); // 添加一个字符串元素
array.append(123); // 添加一个数字元素
root["array"] = array; // 将数组添加到JSON对象中

4. 嵌套JSON对象

        你可以在JSON对象内部添加其他JSON对象作为成员。

Json::Value nestedObject;
nestedObject["nestedKey"] = "nestedValue";
root["nested"] = nestedObject; // 将嵌套对象添加到JSON对象中

5. 生成JSON字符串

        使用Json::StreamWriterBuilderJson::writeString()方法将Json::Value对象转换为JSON格式的字符串。

Json::StreamWriterBuilder writer;
std::string jsonString = Json::writeString(writer, root);

注意事项
  • 当你向Json::Value对象添加成员时,如果该成员已经存在,那么新值将覆盖旧值。
  • jsoncpp不会自动转义字符串中的特殊字符。如果需要转义,你可能需要手动处理或确保你的字符串是安全的。
  • 在处理JSON数据时,确保你的数据类型与JSON类型相匹配。例如,不要将字符串赋值给期望为数字的JSON成员。
  • 生成的JSON字符串将遵循标准的JSON格式,包括使用双引号包围字符串、使用逗号分隔成员等。

示例:生成一个完整的JSON对象
#include <json/json.h>
#include <iostream>int main() {Json::Value root;root["name"] = "John Doe";root["age"] = 30;root["isStudent"] = false;Json::Value hobbies;hobbies.append("reading");hobbies.append("swimming");root["hobbies"] = hobbies;Json::Value address;address["street"] = "123 Main St";address["city"] = "Anytown";root["address"] = address;Json::StreamWriterBuilder writer;std::string jsonString = Json::writeString(writer, root);std::cout << jsonString << std::endl;return 0;
}

输出可能类似于:

{"name": "John Doe","age": 30,"isStudent": false,"hobbies": ["reading","swimming"],"address": {"street": "123 Main St","city": "Anytown"}
}

        在C++中,使用jsoncpp库接收JSON数据主要涉及到解析JSON字符串以创建一个Json::Value对象,然后从这个对象中读取和访问数据。以下是关于使用jsoncpp接收JSON数据的一些基本规则和步骤:

1. 包含头文件

        首先,你需要包含jsoncpp的头文件。

#include <json/json.h>

2. 解析JSON字符串

        使用Json::Reader类来解析JSON字符串。

std::string jsonString = /* JSON字符串 */;
Json::Reader reader;
Json::Value root;
bool parsingSuccessful = reader.parse(jsonString, root);if (!parsingSuccessful) {// 解析失败,处理错误std::cerr << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;// 退出或采取其他错误处理措施
}

3. 访问JSON对象成员

        使用[]运算符或get()方法来访问JSON对象的成员。

if (root.isMember("key")) {std::string value = root["key"].asString();// 或者使用get方法,并提供默认值以防成员不存在std::string valueWithDefault = root.get("key", "default_value").asString();
} else {// 成员不存在,处理这种情况
}

4. 访问JSON数组元素

        使用size()方法获取数组大小,并使用索引来访问数组元素。

if (root.isArray()) {for (Json::Value::ArrayIndex i = 0; i < root.size(); ++i) {std::string element = root[i].asString();// 处理每个元素}
}

5. 访问嵌套JSON对象

        如果JSON对象包含嵌套的对象或数组,你可以通过链式访问来读取它们。

if (root.isMember("nested") && root["nested"].isObject()) {std::string nestedValue = root["nested"]["nestedKey"].asString();
}

6. 类型检查和转换

        在访问JSON值之前,最好先检查其类型,以确保你正在处理正确的数据类型。jsoncpp提供了各种is...()方法来检查值的类型。

if (root["key"].isString()) {std::string value = root["key"].asString();
} else if (root["key"].isInt()) {int value = root["key"].asInt();
}
// ... 其他类型检查

 

注意事项

  • 确保你传入的JSON字符串是有效的,否则Json::Reader将无法正确解析它。
  • 在访问成员或元素之前,总是进行类型检查和存在性检查,以避免程序崩溃或产生意外的结果。
  • 使用默认值可以避免因成员不存在而导致的程序错误,但你应该清楚地了解使用默认值的含义和后果。
  • jsoncpp不会自动转换数据类型。例如,如果你尝试将一个数字作为字符串读取,而该值实际上不是字符串,那么程序将不会按预期工作。

示例:接收一个完整的JSON对象
#include <json/json.h>
#include <iostream>int main() {std::string jsonString = R"({"name": "John Doe","age": 30,"isStudent": false,"hobbies": ["reading", "swimming"],"address": {"street": "123 Main St","city": "Anytown"}})";Json::Reader reader;Json::Value root;bool parsingSuccessful = reader.parse(jsonString, root);if (!parsingSuccessful) {std::cerr << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;return 1;}if (root.isMember("name")) {std::cout << "Name: " << root["name"].asString() << std::endl;}if (root.isMember("age") && root["age"].isInt()) {std::cout << "Age: " << root["age"].asInt() << std::endl;}if (root.isMember("hobbies") && root["hobbies"].isArray()) {std::cout << "Hobbies:" << std::

使用json序列化和反序列化的版本

#pragma once#include <iostream>
#include <string>
#include <json/json.h>//#define MySelf 1const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";std::string Encode(std::string &content)
{std::string package = std::to_string(content.size());package += protocol_sep;package += content;package += protocol_sep;return package;
}// "len"\n"x op y"\nXXXXXX
// "protocolnumber"\n"len"\n"x op y"\nXXXXXX
bool Decode(std::string &package, std::string *content)
{std::size_t pos = package.find(protocol_sep);if(pos == std::string::npos) return false;std::string len_str = package.substr(0, pos);std::size_t len = std::stoi(len_str);// package = len_str + content_str + 2std::size_t total_len = len_str.size() + len + 2;if(package.size() < total_len) return false;*content = package.substr(pos+1, len);// earse 移除报文 package.erase(0, total_len);package.erase(0, total_len);return true;
}// json, protobuf
class Request
{
public:Request(int data1, int data2, char oper) : x(data1), y(data2), op(oper){}Request(){}
public:bool Serialize(std::string *out){
#ifdef MySelf// 构建报文的有效载荷// struct => string, "x op y"std::string s = std::to_string(x);s += blank_space_sep;s += op;s += blank_space_sep;s += std::to_string(y);*out = s;return true;
#elseJson::Value root;root["x"] = x;root["y"] = y;root["op"] = op;// Json::FastWriter w;Json::StyledWriter w;*out = w.write(root);return true;
#endif}bool Deserialize(const std::string &in) // "x op y"{
#ifdef MySelfstd::size_t left = in.find(blank_space_sep);if (left == std::string::npos)return false;std::string part_x = in.substr(0, left);std::size_t right = in.rfind(blank_space_sep);if (right == std::string::npos)return false;std::string part_y = in.substr(right + 1);if (left + 2 != right)return false;op = in[left + 1];x = std::stoi(part_x);y = std::stoi(part_y);return true;
#elseJson::Value root;Json::Reader r;r.parse(in, root);x = root["x"].asInt();y = root["y"].asInt();op = root["op"].asInt();return true;
#endif}void DebugPrint(){std::cout << "新请求构建完成:  " << x << op << y << "=?" << std::endl;}
public:// x op yint x;int y;char op; // + - * / %
};class Response
{
public:Response(int res, int c) : result(res), code(c){}Response(){}
public:bool Serialize(std::string *out){
#ifdef MySelf// "result code"// 构建报文的有效载荷std::string s = std::to_string(result);s += blank_space_sep;s += std::to_string(code);*out = s;return true;
#elseJson::Value root;root["result"] = result;root["code"] = code;// Json::FastWriter w;Json::StyledWriter w;*out = w.write(root);return true;
#endif}bool Deserialize(const std::string &in) // "result code"{
#ifdef MySelfstd::size_t pos = in.find(blank_space_sep);if (pos == std::string::npos)return false;std::string part_left = in.substr(0, pos);std::string part_right = in.substr(pos+1);result = std::stoi(part_left);code = std::stoi(part_right);return true;
#elseJson::Value root;Json::Reader r;r.parse(in, root);result = root["result"].asInt();code = root["code"].asInt();return true;
#endif}void DebugPrint(){std::cout << "结果响应完成, result: " << result << ", code: "<< code << std::endl;}
public:int result;int code; // 0,可信,否则!0具体是几,表明对应的错误原因
};

再谈OSI七层模型

        我们在大致的实现了网络计算器后,接下来再次感受一下下面的这张OSI七层模型,我们可以很惊喜的发现,传输层实际上就包含了我们封装的socket。而会话层则对应着我们封装的服务器,他负责维护整个链接的状况。而表示层不就是对应着我们的序列化和反序列化->添加报头等处理->定制固有的数据格式,不就是我们制定的协议吗?应用层不就是对应着我们的计算、处理方法吗?

 


                       感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/791492.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

抽象类与抽象方法(或abstract关键字)

由来 举例1&#xff1a; 随着继承层次中一个个新子类的定义&#xff0c;类变得越来越具体&#xff0c;而父类则更一般&#xff0c;更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象&#xff0c;以至于它没有具体的实例&#xff0c;这样的类叫做…

STM32G系 编程连接不上目标板,也有可能是软件不兼容。

由于一直用的老版本STM32 ST-LINK Utility 4.20 &#xff0c;找遍了所有问题&#xff0c;SWD就是连不上目标板。 电源脚 VDDA 地线&#xff0c;SWD的四条线&#xff0c;还是不行&#xff0c;浪费了一天&#xff0c;第二天才想起&#xff0c;是不是G系升级了 SWD协议。结果下载…

[InternLM训练营第二期笔记]2. 轻松分钟玩转书生·浦语大模型趣味 Demo

该系列是上海AI Lab举行的书生 浦语大模型训练营的相关笔记部分。 该笔记是第二节课&#xff0c;完成对话、多模态等demo&#xff0c;形成对InternLM的初步了解 1. 部署InternLM2-Chat-1.8B InternLM2-Chat-1.8B是一个对话小模型&#xff0c;只有1.8B参数&#xff0c;因此运行…

深度学习:神经网络模型的剪枝和压缩简述

深度学习的神经网路的剪枝和压缩&#xff0c;大致的简述&#xff0c; 主要采用&#xff1a; network slimming&#xff0c;瘦身网络... 深度学习网络&#xff0c;压缩的主要方式&#xff1a; 1.剪枝&#xff0c;nerwork pruing&#xff0c; 2.稀疏表示&#xff0c;sparse rep…

每日面经分享(python进阶 part2)

Python中的装饰器和上下文管理器区别是什么&#xff1f;它们分别适用于哪些场景&#xff1f; a. 装饰器用于在函数或类的外部添加额外功能&#xff0c;而上下文管理器用于管理资源的获取和释放。 b. 装饰器是一种用于修改函数或类行为的技术。适用于需要在函数或类的外部添加额…

鸿蒙实战开发-通过输入法框架实现自绘编辑框

介绍 本示例通过输入法框架实现自会编辑框&#xff0c;可以绑定输入法应用&#xff0c;从输入法应用输入内容&#xff0c;显示和隐藏输入法。 效果预览 使用说明 1.点击编辑框可以绑定并拉起输入法&#xff0c;可以从输入法键盘输入内容到编辑框。 2.可以点击attach/dettac…

【学习分享】小白写算法之冒泡排序篇

【学习分享】小白写算法之冒泡排序篇 前言一、什么是冒泡排序算法二、冒泡排序算法如何实现三、C语言实现算法四、复杂度计算五、算法稳定性六、小结 前言 最近我要学习下数据结构和算法&#xff0c;有兴趣的小伙伴可以点个关注&#xff0c;一起学习。争取写的浅显易懂。如果你…

解决Toad for Oracle显示乱中文码问题

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

数据结构记录

之前记录的数据结构笔记&#xff0c;不过图片显示不了了 数据结构与算法(C版) 1、绪论 1.1、数据结构的研究内容 一般应用步骤&#xff1a;分析问题&#xff0c;提取操作对象&#xff0c;分析操作对象之间的关系&#xff0c;建立数学模型。 1.2、基本概念和术语 数据&…

CANoe自带的TCP/IP协议栈中TCP的keep alive机制是如何工作的

TCP keep alive机制我们已经讲过太多次,车内很多控制器的TCP keep alive机制相信很多开发和测试的人也配置或者测试过。我们今天想知道CANoe软件自带的TCP/IP协议栈中TCP keep alive机制是如何工作的。 首先大家需要知道TCP keep alive的参数有哪些?其实就三个参数:CP_KEEP…

Qt QML的枚举浅用

QML的枚举用法 序言概念命名规则在QML定义枚举的规范 用法QML的枚举定义方法供QML调用的&#xff0c;C的枚举定义方法 序言 概念 QML的枚举和C的其实差不多&#xff0c;但是呢&#xff0c;局限比较多&#xff0c;首先不能在main.qml里定义&#xff0c;也不能在子项中定义。 …

C++语言学习(二)——⭐缺省参数、函数重载、引用

1.⭐缺省参数 &#xff08;1&#xff09;缺省参数概念 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时&#xff0c;如果没有指定实参则采用该形参的缺省值&#xff0c;否则使用指定的实参。 void Func(int a 0) {cout<<a<<endl; } int…

MySQL的基本操作(超详细)

&#x1f468;‍&#x1f4bb;作者简介&#xff1a;&#x1f468;&#x1f3fb;‍&#x1f393;告别&#xff0c;今天 &#x1f4d4;高质量专栏 &#xff1a;☕java趣味之旅 &#x1f4d4;&#xff08;零基础&#xff09;专栏&#xff1a;MSQL数据库 欢迎&#x1f64f;点赞&…

flutter官方案例context_menus【搭建与效果查看】【省时】

案例地址 https://github.com/flutter/samples/tree/main/context_menus 1&#xff1a;运行查看有什么可以快捷使用的&#xff0c;更新了些什么&#xff0c;可不可以直接复制粘贴 主要内容&#xff1a;在web端中模拟手机类型的点击长按操作&#xff0c;不能直接运行在安卓与io…

如何处理Jenkins打包npm install没有拉取到最新依赖的问题

问题背景&#xff1a; 我们项目中有私有依赖包 frame&#xff0c;是私有服务器上通过 npm 去管理。frame包 publish 之后&#xff0c;通过Jenkins打包时&#xff0c;npm install 一直没有拉取最新的代码。 思考&#xff1a;通过在本地直接替换 node_modules 里的 frame 包&…

element-ui divider 组件源码分享

今日简单分享 divider 组件&#xff0c;主要有以下两个方面&#xff1a; 1、divider 组件页面结构 2、divider 组件属性 一、组件页面结构 二、组件属性 2.1 direction 属性&#xff0c;设置分割线方向&#xff0c;类型 string&#xff0c;horizontal / vertical&#xff0…

SQLite下一代查询规划器(十)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite 查询优化器概述&#xff08;九&#xff09; 下一篇&#xff1a;SQLite的架构&#xff08;十一&#xff09; 1. 引言 “查询规划器”的任务是弄清楚 找出完成 SQL 语句的最佳算法或“查询计划”。 从 SQLi…

VS2013报错The request was aborted: Could not create SSL/TLS secure channel.

问题描述 Visual Studio 2013 Nuget&#xff08;扩展和更新&#xff09;无法连接网络分析和解决方法A connection to the server could not be established because the following error(s) occurred&#xff1a; The request was aborted: Could not create SSL/TLS secure ch…

【问题处理】银河麒麟操作系统实例分享,理光打印机lpr协议打印问题处理

1.问题环境 系统版本&#xff1a;Kylin-Desktop-V10-SP1-General-Release-xxx-20221120-x86_64 内核版本&#xff1a;linux 5.4.18-44kt-generic 系统版本&#xff1a;麒麟v10 sp1 处理器&#xff1a;kx6640ma 2.问题描述 问题详细描述&#xff1a;用户通过lpr协议去连接…

如何申请Telegram机器人 | 推送通知

一、前言 利用Telegram机器人推送通知&#xff0c;需要在环境变量填入正确的TG_BOT_TOKEN以及TG_USER_ID&#xff0c;以下教程简明阐述如何获取Token以及UserID 二、获取步骤 1、首先在Telegram上搜索BotFather机器人。需要注意的是&#xff0c;搜索结果中选择ID为BotFather…