目录
- `网络版计算器`
- `序列化 和 反序列化`
- `重新理解 read、write、recv、send 和 tcp 为什么支持全双工`
- `自定义协议期望的报文格式`
- `模板方法模式实现Socket封装`
- `下面给出序列化与反序列化的关键代码:`
- `序列与反序列化的调用逻辑代码`
- `Jsoncpp`
- `序列化`
- `反序列化`
网络版计算器
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.
约定方案:
- 定义结构体来表示我们需要交互的信息;
- 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
- 这个
过程叫做 "序列化" 和 "反序列化"
序列化 和 反序列化
只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是 ok 的. 这种约定
, 就是 应用层协议
为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。
- 我们要引入序列化和反序列化,直接采用现成的方案 –
jsoncpp库
- 我们要对 socket 进行字节流的读取处理
重新理解 read、write、recv、send 和 tcp 为什么支持全双工
- 在任何一台主机上,
TCP 连接既有发送缓冲区,又有接受缓冲区
,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工(每一个连接内部都有发送与接收缓冲区
)。 - 这就是为什么一个 tcp sockfd 读写都是它的原因
- 在实际数据什么时候发,发多少,出错了怎么办,由 TCP 控制,所以
TCP 叫做传输控制协议
- 当我们调用 write 或 send 等系统调用将数据发送给另一台主机时,这些操作实际上只是将数据内容拷贝到本地系统的发送缓冲区中。至于数据的实际发送时间、发送方式以及错误处理,则完全由传输层协议(如TCP)负责决定和控制,这也是
TCP被称为传输控制协议的原因
。因此,严格来说,send、write 以及对应的接收函数 recv、read 主要是负责数据在应用程序与内核缓冲区之间的拷贝
,而不是直接负责数据的网络传输。
自定义协议期望的报文格式
- 我们把tcp中读到的报文可能有半个,或则一个半等叫做TCP粘报问题。
模板方法模式实现Socket封装
模板方法设计模式是一种行为型设计模式,它定义了一个算法的骨架,将一些步骤延迟到子类中实现。这种设计模式允许子类在不改变算法结构的情况下,重新定义算法的某些步骤。
模板方法模式定义了一个操作中的算法的框架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
#pragma once#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <memory>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
#include <functional>#include "Log.hpp"
#include "InetAddr.hpp"class Socket;
using Socket_ptr = std::shared_ptr<Socket>;
const static int g_backlog = 8;enum EixtError
{SOCK_ERROE = 1,BIND_ERROR,LISTEN_ERROR,USAGE_ERROR
};class Socket
{
public:virtual void BuildSocket() = 0;virtual void BindSocket(InetAddr &addr) = 0;virtual void ListenSocket() = 0;virtual Socket_ptr Accepter(InetAddr *addr) = 0;virtual bool ConnectSocket(InetAddr &addr) = 0;virtual int Getsockfd() = 0;virtual int revc(std::string *out) = 0;virtual int send(std::string &in) = 0;public:void CreateTcpSocket(InetAddr &addr){BuildSocket();BindSocket(addr);ListenSocket();}bool ClientConnect(InetAddr &addr){BuildSocket();return ConnectSocket(addr);}
};class TcpSocket : public Socket
{
public:TcpSocket(int fd = -1) : _sockfd(fd){}void BuildSocket() override{// 创建流式套接字_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){LOG(FATAL, "socket create failed...");exit(SOCK_ERROE);}LOG(DEBUG, "socket create success..._sockfd:%d", _sockfd);}void BindSocket(InetAddr &addr) override{// 绑定struct sockaddr_in Seraddr;memset(&Seraddr, 0, sizeof(Seraddr));Seraddr.sin_family = AF_INET;Seraddr.sin_port = htons(addr.Getport());Seraddr.sin_addr.s_addr = inet_addr(addr.GetIP().c_str());int n = bind(_sockfd, (struct sockaddr *)&Seraddr, sizeof(Seraddr));if (n < 0){LOG(FATAL, "socket bind failed...");exit(BIND_ERROR);}LOG(DEBUG, "socket bind success...");}void ListenSocket() override{// listenint n = listen(_sockfd, g_backlog);if (n < 0){LOG(FATAL, "socket listen failed...");exit(LISTEN_ERROR);}}Socket_ptr Accepter(InetAddr *addr) override{struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int sockfd = accept(_sockfd, (struct sockaddr *)&clientaddr, &len);if (sockfd < 0){LOG(WARNING, "accept failed...");return nullptr;}LOG(DEBUG, "link success... sockfd:%d", sockfd);*addr = clientaddr;Socket_ptr sockptr = std::make_shared<TcpSocket>(sockfd);return sockptr;}bool ConnectSocket(InetAddr &addr) override{struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(addr.Getport());seraddr.sin_addr.s_addr = inet_addr(addr.GetIP().c_str());// 不需要显示的bind,系统自动bind// 客户端连接服务端int n = connect(_sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if (n < 0){std::cout << "connet fail..." << std::endl;return false;}return true;}int revc(std::string *out) override{char inbuffer[1024];ssize_t n = recv(_sockfd, inbuffer, sizeof(inbuffer), 0);if (n > 0){inbuffer[n] = 0;*out += inbuffer; //}return n;}int send(std::string &in) override{int n = ::send(_sockfd, in.c_str(), in.size(), 0);return n;}int Getsockfd() override{return _sockfd;}private:int _sockfd;
};
下面给出序列化与反序列化的关键代码:
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>const std::string SEP = "\r\n";
std::string AddHeader(const std::string &Jsonstr) // 将Json字符串加报头
{std::string lenstr = std::to_string(Jsonstr.size());string str = lenstr;str += SEP;str += Jsonstr;str += SEP;return str;
}
std::string Decode(std::string &inbuffer) // 判断收到的请求是否完整,比如有些请求是分多次收到的,所以需要判断是否完整
{auto pos = inbuffer.find(SEP); //找到第一个\r\n的位置std::string len_str = inbuffer.substr(0,pos); //将有效载荷的长度计算出来并转为字符串if(len_str.empty()) return string(); //判断有效载荷的长度是否为空 int len = stoi(len_str); //将有效载荷的长度转为int类型int total = len+len_str.size()+2*SEP.size(); //计算一个完整的报头的长度if(inbuffer.size()<total) return string(); //如果请求的总大小小于一个报头的长度,说明请求不完整,返回空字符串std::string package = inbuffer.substr(pos+SEP.size(),len); //将完整的请求取出来inbuffer.erase(0,total); //将处理过的请求从缓冲区中删除return package; //返回完整的请求
}class Request
{
public:Request(){}Request(int x, int y, char op) : _x(x), _y(y), _op(op){}bool Serialize(std::string *out) // 序列化,将结构体数据转为字符串{Json::Value root; root["_x"] = _x; // 给Json::Value对象赋值root["_y"] = _y; root["_op"] = _op;Json::FastWriter writer; // 构造一个Json::FastWriter对象std::string str = writer.write(root); // 调用write方法将Json::Value对象转为字符串*out = str;return true;}bool Deserialize(const std::string &in) // 反序列化,将字符串转为结构体数据{Json::Value root;Json::Reader reader; // 构造一个Json::Reader对象bool ret = reader.parse(in, root); // 调用parse方法将字符串转为Json::Value对象if (ret == false){return false;}_x = root["_x"].asInt(); // 调用asInt方法将Json::Value对象转为int类型_y = root["_y"].asInt();_op = root["_op"].asInt();return true;}~Request(){}public:int _x; //_x _op _yint _y;char _op;
};class Response
{
public:Response() {}Response(int result, int code) : _result(result), _code(code){}bool Serialize(std::string *out) // 序列化{Json::Value root;root["_result"] = _result;root["_code"] = _code;Json::FastWriter writer;std::string str = writer.write(root);*out = str;return true;}bool Deserialize(const std::string &in) // 反序列化{Json::Value root;Json::Reader reader;bool ret = reader.parse(in, root);if (ret == false){return false;}_result = root["result"].asInt();_code = root["code"].asInt();return true;}~Response(){}
public:int _result;int _code;
};
序列与反序列化的调用逻辑代码
服务端
while (true){// 1.接受到请求int n = sockfd->revc(&inbuffer);if (n < 0){LOG(DEBUG, "client %s quit...", clientMessage);break;}// 2.检查是否是完整的请求std::string package = Decode(inbuffer); //检查是否是一个完整请求,如果有多个完整请求,提取出来if (package.empty()) continue;// 3.将请求反序列化req.Deserialize(inbuffer);// 4. 请求处理Response resp = _ct(req);// 5. 将处理结果序列化std::string send_str;resp.Serialize(&send_str);// 6. 添加报头send_str = AddHeader(send_str);// 7. 发送响应sockfd->send(send_str);}
客户端
// 1. 构建一个请求// 2. 对请求进行序列化std::string send_str;req->Serialize(&send_str);// 3. 添加长度报头send_str = AddHeader(send_str);// 4. "len"\r\n"{}"\r\n// 发送请求cli->Send(send_str);// 5. 读取应答int n = cli->Recv(&inbuffer);if (n <= 0)break;std::string package = Decode(inbuffer);if (package.empty())continue;// 6. 我能保证package一定是一个完整的应答!// 6.1 构建一个应答auto resp = factory.BuildResponse();// 6.1 应答传入将其反序列化resp->Deserialize(package);// 7. 得到了结构化应答数据
Jsoncpp
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。
当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时,确实存在不同的做法和工具类可供选择。以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍:
安装:
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
序列化
序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件中。Jsoncpp 提供了多种方式进行序列化:
使用 Json::FastWriter:
bool Serialize(std::string *out) // 序列化,将结构体数据转为字符串{Json::Value root; root["_x"] = _x; // 给Json::Value对象赋值root["_y"] = _y; root["_op"] = _op;Json::FastWriter writer; // 构造一个Json::FastWriter对象std::string str = writer.write(root); // 调用write方法将Json::Value对象转为字符串*out = str;return true;}
反序列化
反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp 提供了以下方法进行反序列化:
使用 Json::Reader:
bool Deserialize(const std::string &in) // 反序列化,将字符串转为结构体数据{Json::Value root;Json::Reader reader; // 构造一个Json::Reader对象bool ret = reader.parse(in, root); // 调用parse方法将字符串转为Json::Value对象if (ret == false){return false;}_x = root["_x"].asInt(); // 调用asInt方法将Json::Value对象转为int类型_y = root["_y"].asInt();_op = root["_op"].asInt();return true;}