目录
1. 理解协议
1.1 结构化数据的传输
序列化与反序列化
代码感知:
Request 类
1. 构造函数
2. 序列化函数:Serialize()
3. 反序列化函数:DeSerialize()
补充
4. 成员变量
Response 类
1. 构造函数
2. 序列化函数:Serialize()
3. 反序列化函数:DeSerialize()
4. 成员变量
总结
2. 实验:网络版计算器
2.1 定义哀求和相应协议
2.2 TCP 服务端设计
2.3 业务处理逻辑
3. TCP 客户端实现
4. 序列化与反序列化的重要性
在网络编程中,协议是一个关键概念。协议本质上是一种“约定”,规定了两方在通讯时如何格式化和处理数据。本文将深入探究如何通过协议进行结构化数据的传输,并且通过一个具体的网络版计算器( TCP服务器-客户端)示例,展示序列化与反序列化的实现。
学习导图:
1. 理解协议
协议,简朴来说,就是通讯双方都服从的规则。在前面的例子中,我们使用了父亲和儿子通过电话沟通的场景。父亲告诉儿子会在特定时间打电话,这就是一种约定——协议。
1.1 结构化数据的传输
在网络通讯中,数据通常以字节省的形式发送和吸收。当我们必要传输的是结构化数据时,比方在QQ群聊中,除了笔墨消息外,还包含头像、时间和昵称。这些信息都必要以某种方式发送给对方。假如我们逐个发送这些数据,不仅麻烦,吸收方也难以处理,因此必要对这些数据进行打包。
为什么要把字符串转成结构化数据呢?未来这个结构化的数据肯定是一个对象,然后使用它的时候,直接对象.url 、对象.time 拿到。
而这里的结构体如message就是传说中的业务协议。
由于它规定了我们谈天时网络通讯的数据。
序列化与反序列化
为了简化结构化数据的传输,我们通常将多个独立的信息归并为一个报文。这就是序列化的过程:将数据打包成一个字符串或字节省,再通过网络发送。吸收方收到数据后,必要通过反序列化,将收到的数据解析回原来的结构化数据。
代码感知:
这段代码的焦点功能是实现哀求(Request)和相应(Response)的序列化与反序列化。序列化的作用是将类中的成员变量转换成字符串格式,方便在网络中传输;反序列化的作用是将字符串解析回类的成员变量,规复为结构化数据。以下是对该代码的详细表明:
Request 类
- class Request
- {
- public:
- // 定义常量字符串分隔符和其长度
- static const char SPACE = ' ';
- static const int SPACE_LEN = 1;
复制代码 这个类表现客户端发送给服务器的计算哀求,包含两个操纵数(_x 和 _y)以及一个操纵符(_op)。它提供了序列化和反序列化的能力。
1. 构造函数
这个是默认构造函数,不进行任何初始化操纵,只是声明了 Request 对象。
- Request(int x, int y, int op)
- : _x(x), _y(y), _op(op)
- {}
复制代码 这是一个带参数的构造函数,它担当两个整数操纵数 x、y 和一个字符操纵符 op,并将它们赋值给类中的成员变量 _x、_y、_op。
2. 序列化函数:Serialize()
- std::string Serialize()
- {
- std::string str;
- str = std::to_string(_x);
- str += SPACE;
- str += _op;
- str += SPACE;
- str += std::to_string(_y);
- return str;
- }
复制代码
- 功能:将 Request 对象中的数据成员 _x、_op 和 _y 组合成一个字符串。返回组合好的字符串。最终结果雷同 "1 + 2" 的格式。
3. 反序列化函数:DeSerialize()
- bool DeSerialize(const std::string &str)
- {
- size_t left = str.find(SPACE);
- if(left == std::string::npos)
- {
- return false;
- }
- size_t right = str.rfind(SPACE);
- if (right == std::string::npos)
- {
- return false;
- }
- _x = atoi(str.substr(0, left).c_str());
- _y = atoi(str.substr(right + SPACE_LEN).c_str());
-
- if(left + SPACE_LEN < str.size())
- {
- _op = str[left + SPACE_LEN];
- return true;
- }
- else
- {
- return false;
- }
- }
复制代码
- 功能:从输入的字符串中提取出操纵数 _x 和 _y 以及操纵符 _op,并将它们存储到 Request 对象的成员变量中。
- 步骤:
- str.find(SPACE):在字符串 str 中查找第一个空格的位置,用作分隔符。假如找不到,返回 false。
- str.rfind(SPACE):查找末了一个空格的位置,表现第二个操纵数的开头。假如找不到,返回 false。
- 使用 atoi 函数从字符串中提取整数操纵数 _x 和 _y。substr(0, left) 获取左侧字符串,即第一个操纵数,substr(right + SPACE_LEN) 获取右侧字符串,即第二个操纵数。
- 从字符串 str 中获取操纵符 _op,位于第一个空格后的位置。
- 假如解析成功,返回 true;否则返回 false。
设计思路:
补充
- 上面代码中的atoi是怎么使用的,介绍一下atoi接口
- 是如何从字符串 str 中获取操纵符 _op
- atoi 函数的使用:
atoi 是 C++ 标准库函数之一,它位于 <cstdlib> 头文件中。该函数的作用是将一个字符串(以空字符结尾的字符数组)转换为 int 范例的整数。其原型如下:
- int atoi(const char *str);
复制代码 参数 str 是指向要转换的以空字符结尾的字符串的指针。atoi 会从字符串的开头开始转换,直到碰到第一个非数字字符或到达字符串的结尾。假如字符串以数字开头,atoi 会返回这些数字对应的整数值。假如字符串不是以数字开头,或者字符串为空,atoi 会返回 0。
以下是一些使用 atoi 的例子:
- #include <cstdlib>
- #include <iostream>
- int main() {
- const char *str1 = "123";
- const char *str2 = "12abc34";
- const char *str3 = "abc123";
- int num1 = atoi(str1); // num1 will be 123
- int num2 = atoi(str2); // num2 will be 12
- int num3 = atoi(str3); // num3 will be 0 (no digits at the start)
- std::cout << "num1: " << num1 << std::endl;
- std::cout << "num2: " << num2 << std::endl;
- std::cout << "num3: " << num3 << std::endl;
- return 0;
- }
复制代码
必要注意的是,atoi 不进行错误检查,假如字符串不能完全转换为数字,那么未转换的部分将被忽略。此外,atoi 无法处理整数溢出,假如转换的数字超出了 int 的表现范围,结果是不确定的。
|