网络协议原理

文章目录

  • TCP通信原理
  • TCP与UDP的对比
  • 应用层
    • 应用层协议 --- tcp
    • 协议定制
      • 直接传递对象
      • 自定义协议
      • 现在要解决的问题
      • 业务处理
  • json的使用
    • 使用json进行序列化和反序列化操作
  • 总结

TCP通信原理

tcp是面向字节流的
同时他也是面向连接的
所以TCP的服务器编写代码如图所示:
客户端的编写代码如图:
在这里插入图片描述

tcp在进行connect的时候, 会先进行三次链接, 交换报文, 这一过程叫做TCP的三次握手
这一过程就是客户端的SYN_SENT到服务器的RSTABLISHED过程

而TCP_server在进行, accept的时候, 前提必须是把获取好的连接传输给应用层, 得到新的文件描述符这样才能进行使用

在server的ESTABLISED这一步中, accept会进行返回, 分配新的文件描述符connfd和客户端进行通信
而也在这时表示是握手成功, 需要注意

在connect时只发起一次, 这是一次系统调用, 另外两次由双方的OS自动完成握手的操作
connect与accept可能会发生阻塞, 主要是因为, 他在等待三次握手的链接完成
建立链接, 实际是建立共识, 后续展开~~~~
不完成三次握手, 就不会有后续的整个过程
建立链接完成, 实际上是双方为了维护一条链接, 创建的描述链接的结构体字段

而在udp 的客户端, 没有connect的这一步, 而是直接发送数据

TCP在断开链接的时候, 实际上就是完成了四次挥手.

在这里插入图片描述
其中在client的FIN_WAIT_1的字段表示, 客户端首次发起请求断开的链接
CLOSR_WAIT表示服务器做出相应, 同一断开链接的请求
LAST_ACK表示, 服务端也向客户端发送请求断开链接

而他们的挥手都是close()引起的
这一过程是全双工的
整个过程像是离婚, 双方都要进行确认

TCP与UDP的对比

说到底, 他们无非就是
可靠传输VS不可靠传输
有连接VS无连接
字节流VS数据报

应用层

应用层协议 — tcp

协议本身是一种约定, TCP是面向字节流的, 传输需要正确书写, 现在将重点放在读取的位置处, 我们进行网络版本的计算器的完成(client进行数据的请求, 实际的数据的处理工作由server进行处理)

  1. 在我们的日常通信中, 给对方发送消息包含性别时间, 消息内容等的相关的信息, 但是不能都发送, 而是打包成一体进行发送, 这一过程就会进行序列化的操作
  2. 到我们发送过去的时候, 会将这个一体的信息进行解析, 这是又会变为初始状态的信息的状态. 从一体的状态变成单个的信息状态, 这个过程就是反序列化

那么协议概念也就如图所示了:

在这里插入图片描述
也就是说, 这样的结构化的字段就能被双方进行识别, 双方使用同一种数据类型, 能对指定的字段进行解释, 这是一种约定, 也就是协议
当然, 在应用层不推荐使用结构化字段进行数据的存储和转发到对应的机器, 因为目的机器可能OS不同, 像结构体的内存对齐, 不同类型的指针等的大小都会不同, 所以一般使用序列化处理之后的结果进行转发和处理
那么TCP就是经过序列化处理之后形成的字节流进行转发, 发送过去之后, 进行反序列化得到结构化字段再进行读取数据
序列化:就是将协议对应的结构化字段, 转化成"字符串"的字节流
反序列化: 将"字符串"的字节流, 转化为结构化数据
为什么要这样做呢?
为了方便网络发送
为什么不直接使用传struct的方式呢?而却要在应用层, 使用序列化的方式传递字节流?

  1. 应用层变化快, 因为机器不同导致的问题比较多, 所以更适合使用序列化转化的方式.
  2. 比较好扩展, 未来如果修改协议字段, 序列化的传送也不影响.
  3. Linux内核的各种协议字段都是控制好的, 所以该OS内的进程之间通信不需要使用序列化
  4. 可以跨语言进行使用, 客户端和服务端在未来可能是不同的语言进行写的, 不同的语言使用序列化API之后, 可以响应为对应语言正确的结构化字段

协议定制

直接传递对象

		// 封装一个基类, 用于表示一个socket接口类
		class Socket {
		public:
		    Socket();
		    virtual  ~Socket(){}
		    virtual void CreateSocketOrDie() = 0;// 创建或者死亡
		    virtual void BindSocketOrDie(uint16_t port) = 0;// 绑定或者死亡
		    virtual void ListenSocketOrDie(int backlog) = 0;// 监听或者死亡 这个参数为listen函数的backlog参数,后续讲
		    virtual Socket* AcceptSocketOrDie(std::string *peerip, uint16_t *peerport) = 0;// 接受或者死亡 输出型参数, 获得远端ip和端口
		    virtual void ConnectSocketOrDie(const std::string& serverip, uint16_t serverport) = 0;// 连接或者死亡
		    virtual void CloseSocketOrDie() = 0;// 关闭或者死亡
		    virtual int GetSocketFd() const = 0;// 获取socket文件描述符 
		    virtual void SetSocketFd(int sockfd) = 0;// 设置socket文件描述符
		public:
		    void BuildListenSocket(uint16_t port, int backlog)// 创建一个监听socket
		    {
		        CreateSocketOrDie();// 创建socket
		        BindSocketOrDie(port);// 绑定端口
		        ListenSocketOrDie(backlog);// 监听
		    }
		    void BuildConnectSocket(const std::string& serverip, uint16_t serverport)// 创建一个连接socket
		    {
		        CreateSocketOrDie();// 创建socket
		        ConnectSocketOrDie(serverip, serverport);// 连接
		    }
		    void BuildNormalSocket(int sockfd)// 创建一个普通socket
		    {
		        SetSocketFd(sockfd);
		    }
		};

TcpServerMain.cc

#include "Protocol.hpp"
#include "Socket.hpp"
#include "TcpServer.hpp"
#include <memory>
#include <unistd.h>
#include <iostream>

using namespace Net_Work;

void HandlerRequest(Socket *sockp)
{
    std::cout << "in handler_request" << std::endl;
}

// ./TcpServer port
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;
        return 1;
    }
    uint16_t localport = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> svr(new TcpServer(localport, HandlerRequest));
    svr->Loop();

    return 0;
}

TcpServer.hpp

#pragma once

#include "Socket.hpp"
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
#include <functional>

using func_t = std::function<void(Net_Work::Socket* sockp)>;

class TcpServer;

class THreadData
{
public:
    THreadData(TcpServer *tcp_this, Net_Work::Socket *sockp) : _this(tcp_this), _sockp(sockp)
    {}
public:
    TcpServer *_this;
    Net_Work::Socket *_sockp;
};

class TcpServer{
public:
    TcpServer(uint16_t port, func_t handler_request) : _port(port), _listenSock(new Net_Work::TcpSocket()), _handler_request(handler_request)
    {
        _listenSock->BuildListenSocketMethod(_port, Net_Work::backlog);
    }
    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());// 分离线程
        THreadData *td = static_cast<THreadData*>(args);
        td->_this->_handler_request(td->_sockp);
        
        td->_sockp->CloseSocket();// 关闭套接字
        delete td->_sockp;
        delete td;
        return nullptr;
    }
    void Loop()// 服务器的主循环
    {
        while(true)
        {
            std::string peerip;
            uint16_t peerport;
            // 获取套接字
            Net_Work::Socket* newsock = _listenSock->AcceptSocketOrDie(&peerip, &peerport);// 接受一个连接
            if(newsock == nullptr)// 如果接受失败, 则继续循环
            {
                std::cout << "AcceptSocketOrDie failed" << std::endl;
                continue;
            }
            std::cout << "Get a new connection, sockfd is : " << newsock->GetSocketFd() << ", peer ip: " << peerip << ", peer port: " << peerport << std::endl;
            // 使用线程来处理连接
            pthread_t tid;
            THreadData *td = new THreadData(this, newsock);
            pthread_create(&tid, nullptr, ThreadRun, (void*)td);
        }
    }
    ~TcpServer()
    {
        delete _listenSock;
    }
private:
    int _port;
    Net_Work::Socket *_listenSock;// 创建一个监听socket
public:
    func_t _handler_request;// 处理连接的函数
};

// 至此, 服务端的代码已经完成, 主要功能在HandlerRequest函数中, 该函数需要用户自己实现
// 服务端只是一个框架, 用户需要自己实现HandlerRequest函数来处理连接

TcpClientMain.cc

#include "Protocol.hpp"
#include "Socket.hpp"
#include <iostream>
#include <string>
#include <unistd.h>
// ./TcpClient Serverip Serverport
int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " <Serverip> <Serverport>" << std::endl;
        return 1;
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    Net_Work::Socket* sock = new Net_Work::TcpSocket();
    if(!sock->BuildConnectSocketMethod(serverip, serverport))
    {
        std::cerr << "Connect " << serverip << ":" << serverport << " failed" << std::endl;
        return 1;
    }
    std::cout << "Connect " << serverip << ":" << serverport << " success" << std::endl;
    
    std::string message = "Hello, Server"; 
    write(sock->GetSocketFd(), message.c_str(), message.size());
    
    sock->CloseSocket();
    return 0;
}

Socket.hpp

#pragma once 
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
    
#define Convert(addrptr) ((struct sockaddr*)addrptr) // 这个宏用于将sockaddr_in 转换为struct sockaddr*

namespace Net_Work
{
    const static int defaultSockfd = -1;
    const static int backlog = 5;

    enum{
        Socket_Error = 1,
        Bind_Error,
        Listen_Error
    };

    // 封装一个基类, 用于表示一个socket接口类
    // 设计模式: 模板方法模式, 未来不管是什么socket, 都继承这个类, 实现这些接口, 这样就可以复用这些接口, 这个流程是固定的
    class Socket {
    public:
        virtual  ~Socket(){}
        virtual void CreateSocketOrDie() = 0;// 创建或者死亡
        virtual void BindSocketOrDie(uint16_t port) = 0;// 绑定或者死亡
        virtual void ListenSocketOrDie(int backlog) = 0;// 监听或者死亡 这个参数为listen函数的backlog参数,后续讲
        virtual Socket* AcceptSocketOrDie(std::string *peerip, uint16_t *peerport) = 0;// 接受或者死亡 输出型参数, 获得远端ip和端口
        virtual bool ConnectSocketOrDie(const std::string& serverip, uint16_t serverport) = 0;// 连接或者死亡
        virtual int GetSocketFd() const = 0;// 获取socket文件描述符 
        virtual void SetSocketFd(int sockfd) = 0;// 设置socket文件描述符
        virtual void CloseSocket() = 0;// 关闭socket
    public:
        void BuildListenSocketMethod(uint16_t port, int backlog)// 创建一个监听socket
        {
            CreateSocketOrDie();// 创建socket
            BindSocketOrDie(port);// 绑定端口
            ListenSocketOrDie(backlog);// 监听
        }
        bool BuildConnectSocketMethod(const std::string& serverip, uint16_t serverport)// 创建一个连接socket
        {
            CreateSocketOrDie();// 创建socket
            return ConnectSocketOrDie(serverip, serverport);// 连接
        }
        void BuildNormalSocketMethod(int sockfd)// 创建一个普通socket
        {
            SetSocketFd(sockfd);
        }
    };

    // 继承类Socket, 用于表示一个tcp socket
    class TcpSocket : public Socket {
    public:
        TcpSocket(int sockfd = -1)
            :_sockfd(sockfd)
        {}
        
        ~TcpSocket()
        {}
        void CreateSocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0); 
            if (_sockfd < 0)
            {
                exit(Socket_Error);
            }

        }
        void BindSocketOrDie(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;
            int n = ::bind(_sockfd, Convert(&local), sizeof(local));
            if (n < 0)
            {
                exit(Bind_Error);
            }
        }
        void ListenSocketOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
            {
                exit(Listen_Error);
            }
        }
        Socket* AcceptSocketOrDie(std::string *peerip, uint16_t *peerport)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int newsockfd = ::accept(_sockfd, Convert(&peer), &len);
            if (newsockfd < 0)
            {
                return nullptr;
            }
            *peerport = ntohs(peer.sin_port);
            *peerip = inet_ntoa(peer.sin_addr);
            Socket* newSock = new TcpSocket(newsockfd);
            return newSock;
        }
        bool ConnectSocketOrDie(const std::string& serverip, uint16_t serverport) override
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = htons(serverport);
            server.sin_addr.s_addr = inet_addr(serverip.c_str());
            int n = ::connect(_sockfd, Convert(&server), sizeof(server));
            if (n == 0)
            {
                return true;
            }
            return false;
        }

        int GetSocketFd() const override
        {
            return _sockfd;
        }
        void SetSocketFd(int sockfd) override
        {
            _sockfd = sockfd;
        }
        void CloseSocket() override
        {
            if (_sockfd > defaultSockfd)
            {
                ::close(_sockfd);
            }
        }
    private:
        int _sockfd;
    };
}

协议定制文件, Protocol.hpp

#pragma once

#include <iostream>

// 定制协议
class Request {

public:
    Request(int x, int y);
    ~Request();

private:
    int _data_x;// 参数x
    int _data_y;// 参数y
    char _oper;// 操作符 + - * / %
};

class Response {
private:
    int _result;// 结果
    int _code;// 状态码
};


自定义协议

首先进行举例说明
在这里插入图片描述
在这个过程中,

  1. write/send只是拷贝函数, 将数据从缓冲区拷贝到内核或者是将数据从缓冲区拷贝到内核
  2. 发送缓冲区数据, 是什么时候发?发多少?出错了怎么办?

都是由内核决定 — TCP控制协议
TCP协议叫做传输控制协议, 传输和控制的就是数据的拷贝内容

  1. TCP在实际的通信是双方OS之间的通信, 将数据从一台OS拷贝给另一台OS
  2. 像之前的recv/read也只是与这些功能类似
  3. 而read/write/send/recv…这些功能, 与当时文件的IO过程一模一样, 在内核到用户发送数据的时候, 用户级缓冲区都是处于阻塞等待状态, 当内核的数据到来时, 才会唤醒用户级缓冲区, 进行内容的写入
  4. 而这个从一台机器发送到另一台机器的过程就像是生产者消费者模型, 一边进行发送, 一边进行拿取, 这也是最经典的生产者消费者模型
  5. 一台在发送时, 同时也能完成接收, 这就是全双工协议的TCP, UDP虽然是单工通信, 但是内部与之类似
  6. 数据的粘包问题

用户输入的字符, 发送到对端时, 可能会变少, 这是因为什么呢?
首先, TCP是面向字节流, 没有边界, 而OS在发送TCP数据时, 会通过缓冲区来进行优化, 例如缓冲区的大小为1024字节. 如果一次请求发送的数据量比较少, 没达到缓冲区大小, TCP则会将多个请求合并为一个请求进行发送, 这就形成了粘包问题, 如果一次请求发送的数据量比较大, 超过了缓冲区的大小, TCp就会将其拆分为多次进行发送, 这就是拆包.
实际用户发送多少字节, 并不一定收到多少字节就是这个原因
但是UDP是数据报, 要么都过不去, 要么全部发过去

  1. 除了要定制协议, 还要明确报文与报文之间的边界, 目的就是为了防止数据包的粘包问题(后期代码体现)
  2. UDP是不需要的, 他是面向数据报的协议, 报文与报文之间有明确边界, 而TCp需要自己添加边界, 分为一个一个的数据包, 从用户(上层)看来就是一个一个的字节流, 因此他也叫做面向字节流

现在要解决的问题

  1. 结构化数据的序列化和反序列化
  1. 首先这边传入的数据是 x y 和op操作符, 那么传过去的时候, 最合适的方式就是"x op y\n", 但是为了以后数据序列化的通用性, 这边引入数据头, 那么这个数据就会变成"len\nx op y\n", 我们只需要找到第一个\n就可以知道len的长度是多少, len代表一个数据包的长度, 由此将来传过来的数据流的格式可能是"len\nx op y\n"“len\nx op y\n”"len\nx op y\n"但是可以通过上述的寻找方式都找到
  2. \n都不属于是报文的一部分, 这是一种约定
  3. 这些很多工作都是在做字符串处理
  4. 对Response做序列化和反序列化
bool Serialize(std::string &out)
			    {
			        out.append(std::to_string(_result));
			        out.append(ProtSep);
			        out.append(std::to_string(_code));
			        return true;
			    }
			    bool Deserialize(std::string &in)// 将"_result _code" 反序列化成对象   
			    {
			        auto pos = in.find(ProtSep);
			        if(pos == std::string::npos)
			        {
			            std::cerr << "Response::Deserialize failed! pos == std::string::npos" << std::endl;
			            return false;
			        }
			        _result = std::stoi(in.substr(0, pos));// 刚好是[)区间
			        _code = std::stoi(in.substr(pos+ProtSep.size()));
			        return true;
			    }

  1. 对Request做序列化和反序列化
// 序列化 --- 将对象序列化成字符串
			    bool Serialize(std::string &out)// 序列化都是输出型参数
			    {
			        out.append(std::to_string(_data_x));
			        out.append(ProtSep);
			        out.append(std::to_string(_data_y));
			        out.append(ProtSep);
			        out.append(std::to_string(_oper));
			        return true;
			    }
			    // 反序列化 --- 将字符串反序列化成对象
			    bool Deserialize(std::string &in)// 反序列化传入的必须是 "x op y" 的格式
			    {
			        auto left = in.find(ProtSep);
			        if(left == std::string::npos)
			        {
			            std::cerr << "Request::Deserialize failed! left == std::string::npos" << std::endl;
			            return false;
			        }
			        auto right = in.rfind(ProtSep);
			        if(right == std::string::npos)
			        {
			            std::cerr << "Request::Deserialize failed! right == std::string::npos" << std::endl;
			            return false;
			        }
			        _data_x = std::stoi(in.substr(0, left));
			        _data_y= std::stoi(in.substr(right+ProtSep.size()));// 从当前位置开始截取到ProtSep.size()个字符, 截到结尾
			        std::string oper = in.substr(left+ProtSep.size(), right-(left+ProtSep.size()));
			        if(oper.size() != 1)
			        {
			            std::cerr << "Request::Deserialize failed! oper.size() != 1" << std::endl;
			            return false;
			        }
			        _oper = oper[0];
			        return true;
    }
  1. 上述操作只是完成业务方面的应用, len /n的处理是在网络发送方面先序列化, 换成"x op y"的格式, 然后调用encode, 变成"len\nx op y\n"

完成解析函数

std::string Encode(const std::string &message)// 先序列化, 变成"x op y"的格式, 然后调用Encode, 变成"len\nx op y\n"形式
			    {
			        std::string len = std::to_string(message.size());
			        std::string package = len + LineBreakSep + message + LineBreakSep;
			        return package;
			    }
			    // 解析的时候, 无法保证传进来的package一定是一个完整的协议, 所以需要传入一个指针, 指向一个字符串, 将解析出来的协议内容放入这个字符串中
			    bool Decode(std::string &package, std::string *message)// 先解码, 变成"len\nx op y\n"形式, 然后反序列化, 变成对象
			    {
			        auto pos = package.find(LineBreakSep);// 确保拿到的长度是一个完整的协议
			        if (pos == std::string::npos)
			        {
			            std::cerr << "Decode failed! pos == std::string::npos" << std::endl;
			            return "";
			        }
			        std::string lens = package.substr(0, pos);// 拿到协议的长度
			        int message_len = std::stoi(lens);// 将长度转换为整数
			        int total_len = message_len + pos + LineBreakSep.size() + 2 * LineBreakSep.size();// 确保长度是完整的协议的长度
			        if (package.size() < total_len)// 如果长度不够, 返回false
			        {
			            std::cerr << "Decode failed! package.size() < message_len + pos + LineBreakSep.size()" << std::endl;
			            return false;
			        }
			        // 走到这里, 说明长度至少是一个完整的协议的长度
			        *message = package.substr(pos + LineBreakSep.size(), message_len);// 将协议内容赋值给message
			        package.erase(0, total_len);// 删除已经解析出来的协议, 保证下一次解析的时候, 不会重复解析
			        return true;
			    }

业务处理

			void HandlerRequest(Socket *sockp)
			{
			    std::string inbufferstream;// 接收请求的缓冲区
			    // 1.创建一个工厂类, 用于创建请求对象
			    std::unique_ptr<Factory> factory = std::make_unique<Factory>();
			    auto req = factory->BuildRequest();
			    // 接收请求
				while(true)
				    {
				        // 1.读取报文
				        if (!sockp->Recv(&inbufferstream, 1024))
				        {
				            break;// 接收失败
				        }
				        // 2.分析收到的字节流, 是否有一个完整的报文
				        std::string message;
				        if(!Decode(inbufferstream, &message))
				        {
				            continue;
				        }
				        // 3.处理请求, 走到这里, 说明已经有一个完整的报文了, 可以进行反序列化
				        if(!req->Deserialize(message))
				        {
				            break;// 反序列化失败
				        }
				        // 4.业务处理, 计算器
				        auto resp = calculate.Cal(req);
				        // 5.将响应对象序列化
				        std::string send_string;
				        resp->Serialize(&send_string);// 序列化之后数据一定是"result code"
				        // 6.添加报头(构建完整的字符串级别的响应报文)
				        send_string = Encode(send_string);
				        // 上述的操作, 可以封装成一个类, 因为序列化的整体流程就是这样的, 封装成一个类可以直接调用, 然后进行后续的工作, 更整体化, 更清晰
				 
				 
				    }
}

现在进行修改代码, 实际的输入内容由上层决定, 当前的回调只是将数据进行发送, 转发即可, 后续的操作就不再演示,省略即可

引入成熟的序列化和反序列化的操作

为了方便操作, 先将自己的序列化方式改为条件编译进行限制

在这里插入图片描述

json的使用

首先, 我们需要知道现如今常用的序列化方式
json — 常用
protobuf — 网络中常用的序列化和反序列化操作
xml – 比较慢, c++不进行考虑

使用json进行序列化和反序列化操作

java python都是可以直接使用json, 而c++要进行使用, 则必须要使用第三方sdk, 安装别人的库

sudo apt-get install -y jsoncpp-devel //devel表示这个版本是开发板

安装完成序列化的演示实现

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root; // 万能类型, 接受任何数据类型
    root["k1"] = 100;
    root["k2"] = 200;
    root["k3"] = 300;
    Json::FastWriter writer;
    std::string str = writer.write(root); // 序列化
    std::cout << str << std::endl;
    return 0;
}

因为jsoncpp是第三方的库, 所以要使用, 必须在编译时进行编译选项的链接

在这里插入图片描述
运行结果演示:
在这里插入图片描述
同时json支持嵌套键值对的定义实现:

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root; // 万能类型, 接受任何数据类型
    root["k1"] = 100;
    root["k2"] = 200;
    root["k3"] = 300;
    Json::Value v;
    v["hello"] = "world";
    v["world"] = "nihao";
    root["k4"] = v;

    Json::FastWriter writer;
    std::string str = writer.write(root); // 序列化
    std::cout << str << std::endl;
    return 0;
}

运行结果演示:
在这里插入图片描述除了使用FastWrite方式还可以使用StyleWrite方式进行:

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root; // 万能类型, 接受任何数据类型
    root["k1"] = 100;
    root["k2"] = 200;
    root["k3"] = 300;
    Json::Value v;
    v["hello"] = "world";
    v["world"] = "nihao";
    root["k4"] = v;
    Json::StyledWriter writer;
    std::string str = writer.write(root); // 序列化
    std::cout << str << std::endl;
    return 0;
}

可以看到他生成的结果是具有格式演示的序列化, 这样只是可读性强一点, 除此之外没有区别
在这里插入图片描述对上述的业务代码完成序列化和反序列化操作:
序列化:

Json::Value root;
root["x"] = _data_x;
root["y"] = _data_y;
root["op"] = _oper; // 字符的本质就是整数, 所以可以直接赋值
Json::FastWriter writer;
*out = writer.write(root); // 也可以判断是否成功, 这边就不进行了
return true;

反序列化:

Json::Value root;
Json::Reader reader;
bool ret = reader.parse(in, root);
if (!ret) // 解析失败, 返回false, 参数为in, 解析结果为root
{
    std::cerr << "Request::Deserialize failed! Json::Reader::parse failed" << std::endl;
    return false;
}
_data_x = root["x"].asInt();
_data_y = root["y"].asInt();
_oper = root["op"].asInt(); // 字符的本质就是整数
return ret;

这边代码的encoed和decode和对我们自己的序列化和反序列化变成的"len\nx op y\n"的格式, 在使用json之后有什么区别呢?会有变化吗?

实际上, 不管是自定义序列化还是json序列化, 他们操作之后的结果或者是操作之前的数据格式都是对这两个方法没有影响的, 因为长度信息都在len这个字符当中, 所以未来不管使用哪种序列化的方法, 他都能进行正确的识别

现在已经完成了对自定义序列化和json序列化的条件编译代码, 要想实现对应的序列化操作还需要对他进行编译代码时的选择, 这边选择在makefile中进行修改

在这里插入图片描述在这里插入图片描述
在编译时加一个 -static表示静态编译, 这样编译出来的代码内部以静态链接的方式连接着其他的第三方库, 所以内存较大, 但是直接运行的效果较好
可以将这个程序改为守护进程的方式
在这里插入图片描述在这里插入图片描述

总结

  1. 上述用到的协议只有一层, Response Request, 未来如果想引入多个协议以供不同的应用场景, 可以将这些协议写好之后保存在一个vector里面, 再在序列化的时候在序列化字段最前面加上protocal_code\nlen\n..., 其中protocal_code就表示使用哪一个协议.
  2. 像是Socket.hpp的整个功能就能表示一个会话层, 用于通信的连接和管理
  3. 我们写的protocal添加报头, 对象转字符串等的操作都是在表示层
  4. 而这个应用层就是我们写的calculate.hpp
  5. 这些我们自己写的三层最终都会汇总到应用层(从OSI模型到TCP/IP模型也是这样)当中, 未来我们写网络服务就一定会用到着三层.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言:在Visual Studio中使用C语言scanf输入%s出现的栈溢出问题

学了C之后就很少使用C语言了&#xff0c;今天帮同学解答C语言问题&#xff0c;遇到了一个我以前没有遇到过的问题。 一、问题描述 先看以下代码&#xff1a; #include<stdio.h> int main() {char str[100] { 0 };scanf_s("%s", str);printf("%s",…

探索极简计算的新边界:从Uxn虚拟机看未来编程生态

越来越多的开发者追求复杂度和功能性的极致,然而,有一个小众的编程社区选择了截然不同的道路——极简主义。Uxn虚拟机便是这一思潮的代表之一。它通过简洁的指令集和有限的硬件资源模拟,试图打造一种可以在多种设备上运行的便携性编程环境。 与主流的重型操作系统和复杂…

Redis面试题——第四篇

1. Redis主从复制的常见拓扑结构有哪些 一主多从&#xff1a;这是最基本的拓扑结构&#xff0c;包含一个主节点和多个从节点&#xff0c;所有写操作都在主节点上执行&#xff0c;而读操作可以在从节点上进行&#xff0c;以提高读取速度和负载均衡。 树状主从结构&#xff1a;从…

模拟电路设计期末速成总结

模拟电路设计期末速成总结 模拟电路设计是电子工程和电气工程专业中的一门重要基础课&#xff0c;主要研究连续时间信号&#xff08;模拟信号&#xff09;的处理和应用。期末复习时&#xff0c;针对这门课可以分为以下几个关键内容进行速成总结。 一、基本概念与元件 模拟信号…

C++设计模式——代理模式

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 引言代理模式的定义代理模式的具体实现 引言 我们经常听到代理服务器「代理服务器是一个中间服务器&#xff0c;能够接收客户端的请求&#xff0c;并代表客户端向服务器发起请求&#xff0c;然后将服…

经典文献阅读之--RGBD GS-ICP SLAM(结合ICP和3D GS构建最快的稠密SLAM)

0. 简介 同时定位与地图构建&#xff08;SLAM&#xff09;的密集表示在机器人技术、虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;应用中扮演了关键角色。在密集表示SLAM的最新进展中&#xff0c;利用神经场景表示和3D高斯表示以实现高保真的空间表…

Mycat引领MySQL分布式部署新纪元:性能与扩展性的双重飞跃

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 目录 前言&#…

使用OneAPI+Ollama+Dify搭建一个兼容OpenAI的API发布及AI应用开发系统(三)Dify的安装及配置

在GitHub中的AI工作流短代码平台中&#xff0c;Dify获星一直名列前茅&#xff0c;目前已达48K星&#xff0c;其工作稳定性也是非常的高&#xff0c;在这里我们介绍一下Dify的安装。 由于Dify的结构非常的复杂&#xff0c;我们这里介绍Docker的方式进行安装&#xff0c;硬件的最…

Nvidia Jetson Orin平台部署CenterPoint模型

最近尝试将CenterPoint模型部署到Orin平台,网络上教程很多,也很杂乱,于是便整理一版自用。 主要根据NVIDIA Lidar AI Solution进行复现。并在此基础上进行补充 Orin平台: python:3.8 CUDA:11.4 torch:1.14.0 torchvision:0.15.1 TensorRT: 8.5.2.1 在Compile &&a…

Java并发编程实战 08 | 彻底理解Shutdown Hook

钩子线程&#xff08;Hook Thread&#xff09;简介 在一个 Java 应用程序即将退出时&#xff08;比如通过正常执行完成或通过用户关闭应用程序&#xff09;&#xff0c;通常需要进行一些清理操作&#xff0c;例如&#xff1a; 释放资源&#xff08;如文件句柄、网络连接&…

解锁C++继承的奥秘:从基础到精妙实践(下)

文章目录 前言&#x1f950;五、多继承&#xff0c;菱形继承和菱形虚拟继承&#x1f9c0;5.1 多继承&#x1f9c0;5.2 菱形继承&#x1f9c0;5.3 虚拟继承&#xff08;解决菱形继承问题&#xff09;5.3.1 虚拟继承的语法&#xff1a;5.3.2 虚拟继承示例&#xff1a; &#x1f9…

大舍传媒-海外媒体发稿:为您打造全球品牌影响力

大舍传媒-海外媒体发稿&#xff1a;为您打造全球品牌影响力 在当今全球化的商业环境中&#xff0c;企业若想在激烈的市场竞争中脱颖而出&#xff0c;拓展全球市场&#xff0c;提升品牌影响力至关重要。大舍传媒的海外媒体发稿服务&#xff0c;正是您实现这一目标的得力助手。 …

面对服务器掉包的时刻困扰,如何更好的解决

在数字化时代&#xff0c;服务器的稳定运行是企业业务连续性的基石。然而&#xff0c;服务器“掉包”现象&#xff0c;即数据包在传输过程中丢失或未能正确到达目的地的情况&#xff0c;却时常成为IT运维人员头疼的问题。它不仅影响用户体验&#xff0c;还可能导致数据不一致、…

【AI 新观察】“转人工!转人工!”——智能客服痛点与破局之路

在当今数字化时代&#xff0c;智能客服在电商等众多领域被广泛应用&#xff0c;然而&#xff0c;一句又一句“转人工&#xff01;转人工&#xff01;”却常常暴露出智能客服存在的痛点。一、智能客服之痛 1. 理解偏差引不满 智能客服在理解客户问题时&#xff0c;常常出现偏差…

mybatisPlus对于pgSQL中UUID和UUID[]类型的交互

在PGSQL中&#xff0c;有的类型是UUID和UUID[]这种类型&#xff0c;在mybatis和这些类型交互的时候需要手动设置类型处理器才可以&#xff0c;这里记录一下类型处理器的设置 /*** UUID类型处理器*/ public class UUIDTypeHandler extends BaseTypeHandler<UUID> {/*** 获…

Golang | Leetcode Golang题解之第478题在圆内随机生成点

题目&#xff1a; 题解&#xff1a; type Solution struct {radius, xCenter, yCenter float64 }func Constructor(radius, xCenter, yCenter float64) Solution {return Solution{radius, xCenter, yCenter} }func (s *Solution) RandPoint() []float64 {r : math.Sqrt(rand.…

热更新解决方案2 —— Lua语法相关知识点

概述 开发环境搭建 Lua语法 1.第一个Lua程序 2.变量 print("******变量*******"); --lua当中的简单变量类型 -- nil number string boolean -- lua 中所有的变量声明 都不需要声明变量类型 它会自动的判断类型 -- 类似C# 中的var --lua中的一个变量 可以随便赋值 ——…

Product1M 深度理解 PPT

系列论文研读目录 文章目录 系列论文研读目录 模态内检索&#xff1a;是指在同一模态&#xff08;例如&#xff0c;图像、文本或音频&#xff09;中进行的检索任务。它通常涉及在同一类型的数据中查找相关项。比如下面图像只能查询图像&#xff0c;文本只能查询文本&#xff0c…

modbus tcp wireshark抓包

Modbus TCP报文详解与wireshark抓包分析_mbap-CSDN博客 关于wireshark无法分析出modbusTCP报文的事情_wireshark 协议一列怎么没有modbus tcp-CSDN博客 使用Wireshark过滤Modbus功能码 - 技象科技 连接建立以后才能显示Modbus TCP报文 modbus.func_code 未建立连接时&…

D36【python 接口自动化学习】- python基础之函数

day36 函数的定义 学习日期&#xff1a;20241013 学习目标&#xff1a;输入输出与文件操作&#xfe63;-49 函数定义&#xff1a;如何优雅地反复引用同一段代码&#xff1f; 学习笔记&#xff1a; 函数的用途 定义函数 调用函数 # 定义函数 def foo():print(foo)print(foo …