当前位置: 首页 > article >正文

【Linux】网络编程:初识协议,序列化与反序列化——基于json串实现,网络通信计算器中简单协议的实现、手写序列化与反序列化

目录

一、什么是协议?

二、为什么需要有协议呢?

三、协议的应用

四、序列化与反序列化的引入

什么是序列化和反序列化?

为什么需要序列化和反序列化?

五、序列化推荐格式之一:JSON介绍

六、网络版计算器编程逻辑


本文所涉及到的两种设计模式:

工厂模式:简单工厂模式、工厂方法模式、抽象工厂模式-CSDN博客

模板方法模式-CSDN博客

一、什么是协议?

在日常生活中,我们无时无刻不在遵守着“协议”。例如:当你点外卖时,你提前告知骑手要把外卖放在小区的5号外卖柜中。当外卖平台给你发送“订单已送达”信息后,你会前往5号外卖柜取出你的外卖。为什么不去6号外卖柜取走你的外卖呢?因为你和骑手已经事先约定好将外卖放在5号外卖柜中,这就是你和该名骑手间事先约定好的“协议”,双方必须同时遵守这一规则,这样才能保证资源处理的正确性。

协议(Protocol)在计算机科学和通信领域中,指的是一组规则或标准,用于规定数据如何在不同的系统、设备或进程之间进行交换和处理。协议定义了通信过程中的语法、语义、同步规则以及可能出现的错误处理方式。通过遵循相同的协议,不同的硬件、软件或网络系统能够相互理解和协作,从而实现信息的有效传输和处理。

二、为什么需要有协议呢?

无论是日常生活还是计算机系统、网络通信中,协议的存在都是至关重要的。它们为不同主体之间的交互提供了规则和标准,确保了沟通和操作的有效性、可靠性以及一致性。无论是人与人之间的交流,还是系统与系统之间的通信,都需要一套共同的语言或规则。协议就像是一种“共同的语言”,确保各方能够理解彼此,并按照预定的方式行动。

  • 日常生活例子:当你打电话订餐时,你和餐厅服务员之间遵循着一种“协议”——你告诉他你想吃什么,他确认并记录你的订单。如果每个人点餐时都使用不同的方式或语言,沟通就会混乱不堪,订单可能会出错。

  • 技术例子:在互联网中,设备和服务器之间通过HTTP协议进行通信。HTTP定义了请求和响应的格式,确保浏览器和服务器能够互相理解并交换信息。如果没有HTTP协议,浏览器可能无法正确解释服务器发送的内容。

三、协议的应用

在之前所学的TCP通信中,客户端与服务端之间的信息传输是面向字节流的。这就意味着无论是客户端还是服务端,我们都需要从套接字中读取完整的请求,并对该请求进行处理,做出相应的应答。但我们真的能保证每次所读取到的数据都是一个完整的请求吗?当客户端一直向服务端发送请求时,服务端一直在接收来自客户端的请求信息,我们如何保证每次从字节流中提取的一定是一个完整的请求呢?——协议!!!

我们事先约定好客户端的每个请求信息和服务端的应答信息遵守如下格式:

请求/应答报文:“len\r\nInformationStr\r\n

我们将上述字符串称之为报文,其中:

len:请求字符串的长度(字符串形式)。

"\r\n":分隔符字符串,用于分隔请求信息和标识一段完整报文的结尾。

InformationStr:请求/应答信息(字符串形式)。

当我们约定好请求与应答信息都要遵守上述格式后,那么对于以后客户端/服务端,当我们需要从字节流中提取到一个完整的报文信息时,我们需要做以下工作来判断当前字节流中是否包含至少一个完整的报文:

1、因为当第一次开始处理字节流时,字节流的起始端一定是包含len或len字符串的一部分。所以我们首先找到分隔符的位置,将len提取出来转化为整数。如果没找到则返回上层继续接收客户端传来的信息。

2、当我们得到信息的长度为len后,开始计算该条完整报文的理论长度。如果报文小于理论长度,返回上层继续接收信息。否则,则说明当前字节流中包含至少一条完整的报文。

3、依据已经得到的InformationStr的起始位置和InformationStr的长度len,将报文中的有效信息部分提取出来。并从现有字节流中删除此次提取的完整的报文(包括分隔符)。

经过上述操作,我们就完成了一次对报文的提取工作。

四、序列化与反序列化的引入

什么是序列化和反序列化?

序列化(Serialization)和反序列化(Deserialization)是计算机科学中用于将数据结构、对象状态或数据内容转换为可以存储或传输的格式的过程。具体来说:

  • 序列化:是将对象、数据结构或数据转换为字节流或某种特定格式的过程,以便于存储到文件、数据库或通过网络传输。序列化后的数据通常是连续的字节流,能够被传输或存储。

  • 反序列化:是将序列化后的字节流或格式化数据还原为原始对象或数据结构的过程,使得应用程序可以重新使用这些数据。

为什么需要序列化和反序列化?

当我们在进行网络通信时,发送的信息可能不单纯是一个字符串,还可能是一个结构体/对象。而对于不同的主机而言,由于不同操作系统、编程语言和硬件架构对内存布局和数据类型的处理方式可能不同,直接将结构体以字节流的形式传输会面临以下几个问题:

  1. 字节序(Endianness)

            不同的硬件架构可能使用不同的字节序(大端序或小端序)来存储整数和浮点数。如果发送方和接收方的字节序不一致,直接传输字节流会导致数据解析错误。
  2. 内存对齐(Memory Alignment)

            不同平台和编译器可能有不同的内存对齐规则。例如,某些平台要求整数必须存储在4字节对齐的地址上,而另一些平台可能没有这样的要求。这会导致结构体在不同平台上占用不同大小的内存。
  3. 数据类型大小

            不同的操作系统和编译器对数据类型(如int、float、double等)的定义可能不同。例如,一个int在32位系统上占4字节,在16位系统上可能占2字节。直接传输结构体可能导致数据解析错误。
  4. 结构体成员顺序

            结构体的成员顺序在不同的平台上可能有所不同。例如,某些编译器可能会根据对齐规则重新排列结构体的成员,导致相同结构的成员在内存中的顺序不同。

这就意味着,我们无法将一个结构体以字节流的形式进行网络通信时的信息的传输,因为这可能面临着不可预知的错误,因此序列化与反序列化就成为了客户端与服务端之间正确通信的必要步骤。

五、序列化推荐格式之一:JSON介绍

JSON是一种轻量级的数据交换格式。它基于JavaScript编程语言的一个子集,但独立于语言,因此许多现代编程语言都提供了对JSON的支持。

JSON的定义与特点

  1. JSON是一种文本格式的结构化数据序列化方式,用于数据交换和存储。
  2. 它具有简洁、易读、易写的特点,同时也易于机器解析和生成。
  3. JSON是完全独立于语言的,这意味着它可以在不同的编程语言之间轻松传递数据。

JSON的数据类型

  1. JSON支持多种数据类型,包括字符串(string)、数值(number)、布尔值(boolean)、对象(object)、数组(array)以及null。
    • 字符串:由双引号包围的文本。
    • 数值:可以是整数或浮点数,不支持八进制和十六进制。
    • 布尔值:true或false。
    • 对象:由花括号{}包围,包含零个或多个“键值对”的集合。
    • 数组:由方括号[]包围,包含零个或多个值的有序集合。

解析与生成JSON

  1. 解析JSON:当接收到JSON格式的数据时,应用程序需要将其解析为内部可操作的数据结构(如对象、数组等)。大多数编程语言都提供了JSON解析库或内置功能来支持这一过程。
  2. 生成JSON:与解析相反,生成过程是将程序内部的数据结构转换为JSON格式的字符串,以便在网络传输或文件存储中使用。同样地,各种编程语言也提供了相应的库或方法来支持这一操作。

在C++中,引入了jsoncpp。jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了一种简单的方式来解析、生成、操作和查询 JSON 数据。其应用见如下文章,此处不再过多赘述。

Jsoncpp使用简介-CSDN博客

六、网络版计算器编程逻辑

Socket.hpp :对套接字进行封装

#pragma once
#include <iostream>
#include <cstring>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include <memory>

#include "Log.hpp"
#include "InternetAddr.hpp"

const static int gblcklog = 8;

class Socket;                             // 先声明
using SockSPtr = std::shared_ptr<Socket>; // 智能指针类型别名

// 模版方法模式
// 基类提供纯虚函数方法,子类需要根据需求设计方法的具体实现
class Socket
{
public:
    virtual void CreateSocketOrDie() = 0;
    virtual void CreateBindOrDie(uint16_t port) = 0;
    virtual void CreateListenOrDie(int backlog = gblcklog) = 0;
    virtual SockSPtr Accepter(InetAddr *cliaddr) = 0;
    virtual bool Conntecor(const std::string &peerip, uint16_t peerport) = 0;
    virtual int Sockfd() = 0;
    virtual void Close() = 0;

    virtual ssize_t Recv(std::string *out) = 0;
    virtual ssize_t Send(const std::string &in) = 0;

public:
    void  BuildListenSocket(uint16_t port)
    {
        CreateSocketOrDie();
        CreateBindOrDie(port);
        CreateListenOrDie();
    }
    bool BuildClientSocket(const std::string &peerip, uint16_t peerport)
    {
        CreateSocketOrDie();
        return Conntecor(peerip, peerport);
    }
    // void BuildUdpSocket()
    // {}
};

class Tcp_Socket : public Socket
{
private:
    int _sockfd;

public:
    Tcp_Socket(int sockfd = -1)
        : _sockfd(sockfd)
    {
    }

    ~Tcp_Socket()
    {
    }

    void CreateSocketOrDie() override
    {
        // 1、 创建套接字
        _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "Sockfd Create False!\n");
            exit(-1);
        }
        LOG(INFO, "Sockfd Create Success!\n");
    }

    void CreateBindOrDie(uint16_t port) override
    {
        struct sockaddr_in local_addr;
        memset(&local_addr, 0, sizeof(local_addr));
        local_addr.sin_addr.s_addr = INADDR_ANY;
        local_addr.sin_port = htons(port);
        local_addr.sin_family = AF_INET;

        // 2、绑定本地ip地址和port端口号
        if (::bind(_sockfd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0)
        {
            LOG(FATAL, "Listen Sockfd Bind False!\n");
            exit(-1);
        }
        LOG(INFO, "Listen Sockfd Bind Success!\n");
    }

    void CreateListenOrDie(int backlog) override
    {
        // 3、将套接字设置为【监听状态】, 以监听来自客户端的连接请求
        if (::listen(_sockfd, backlog) < 0)
        {
            LOG(FATAL, "Listen Sockfd Listen False!\n");
            exit(-1);
        }
        LOG(INFO, "Listen Sockfd Listen Success!\n");
    }

    SockSPtr Accepter(InetAddr *client_addr) override
    {
        // 1、获取来自客户端的连接请求,并获得I/O专用套接字
        struct sockaddr_in from_client;
        socklen_t addr_len = sizeof(from_client);
        memset(&from_client, 0, addr_len);
        int _io_sockfd = accept(_sockfd, (struct sockaddr *)&from_client, &addr_len);
        if (_io_sockfd < 0)
        {
            LOG(FATAL, "Server Sockfd Accept False!");
            return nullptr;
        }
        LOG(DEBUG, "Server Sockfd Accept Success!");
        *client_addr = from_client;
        return std::make_shared<Tcp_Socket>(_io_sockfd);
    }

    bool Conntecor(const std::string &peerip, uint16_t peerport) override
    {
        // 连接服务端
        struct sockaddr_in to_server;
        memset(&to_server, 0, sizeof(to_server));
        inet_pton(AF_INET, peerip.c_str(), &to_server.sin_addr);
        to_server.sin_family = AF_INET;
        to_server.sin_port = htons(peerport);
        if (connect(_sockfd, (struct sockaddr *)&to_server, sizeof(to_server)) < 0)
        {
            return false;
        }
        return true;
    }

    int Sockfd() override
    { 
        return _sockfd;
    }
    void Close() override
    {
        if (_sockfd > 0)
        {
            ::close(_sockfd);
        }
    }

    ssize_t Recv(std::string *out) override
    {
        char inbuffer[4096];
        ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
        if (n > 0)
        {
            inbuffer[n] = 0;
            *out += inbuffer;
        }
        return n;
    }
    ssize_t Send(const std::string &in) override
    {
        return ::send(_sockfd, in.c_str(), in.size(), 0);
    }
};

 Protocol.hpp : 协议头文件,其中约定了报文格式、请求格式、应答格式、序列化与反序列化方法。

#pragma once
#include <memory>
#include <string>
#include <jsoncpp/json/json.h>
#include "Log.hpp"
#include <iostream>
#include <cstring>
// version1 报文:“len\r\njson串\r\n”
// version2 自定义序列串报文:"len\r\nx#operator#y\r\n"
// 分隔字符串
static const std::string separate_str = "\r\n";
static const std::string self_cut_str = "#";
// 网络计算器

// 添加报头
std::string AddCode(const std::string &json_str)
{
    int len = json_str.size();
    std::string len_str = std::to_string(len);
    return len_str + separate_str + json_str + separate_str;
}

// 传递进来的报文可能有以下形式。如果报文不完整,返回空串到上层,让上层继续接收来自客户端的信息。如果能提取到完整json串,在传入报文的基础上将json串拆分出来。
// 不能带const
// "le
// "len"
// "len"\r\n
// "len"\r\n"{json}"\r\n (]
// "len"\r\n"{j
// "len"\r\n"{json}"\r\n"len"\r\n"{
// "len"\r\n"{json}"\r\n
// "len"\r\n"{json}"\r\n"len"\r\n"{json}"\r\n"len"\r\n"{json}"\r\n"len"\r\n"{json}"\r
std::string RemoveCode(std::string &message) // 移除报头,返回提取到的json串
{
    auto _pos = message.find(separate_str); // 先找到分隔符的位置
    if (_pos == std::string::npos)          // 没找到则返回上层继续接收客户端传来的信息
    {
        return std::string();
    }
    std::string json_len_str = message.substr(0, _pos); // 提取出报文首部json串的长度
    int json_len = stoi(json_len_str);
    int _total = json_len + separate_str.size() * 2 + json_len_str.size(); // 计算报文理论长度
    if (message.size() < _total)                                           // 如果报文小于理论长度,返回上层继续接收信息
    {
        return std::string();
    }
    std::string ret_json_str = message.substr(_pos + separate_str.size(), json_len); // 提取报文中的json串
    message.erase(0, _total);                                                        // 在传入报文中删除已经提取的该段报文
    return ret_json_str;                                                             // 返回提取的json串
}

// 客户端发起请求
class Request
{
public:
    Request(int x = -1, int y = -1, char oper = -1)
        : _x(x), _y(y), _operator(oper)
    {
    }

    // 序列化:结构体成员 -》 json串
    bool Serialize(std::string *out_jsonstr)
    {
#ifdef FLAG
        *out_jsonstr = std::to_string(_x) + self_cut_str + _operator + self_cut_str + std::to_string(_y);
        return true;
#else
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["operator"] = _operator;
        Json::FastWriter writer;
        *out_jsonstr = writer.write(root);
        return true;
#endif
    }

    // 反序列化:json串 -》 结构体成员
    bool DeSerialize(const std::string &in_jsonstr)
    {

#ifdef FLAG
        auto x_pos = in_jsonstr.find(self_cut_str);
        if(x_pos == std::string::npos) return false;
        auto y_pos = in_jsonstr.rfind(self_cut_str);
        if(y_pos == std::string::npos) return false;
        if(x_pos + 1 + self_cut_str.size() != y_pos) return false;
        _x = std::stoi(in_jsonstr.substr(0, x_pos));
        _y = std::stoi(in_jsonstr.substr(y_pos + self_cut_str.size()));
        _operator = in_jsonstr[y_pos - 1];
        return true;
#else
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(in_jsonstr, root);
        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _operator = root["operator"].asInt();
        return res;
#endif
    }

    int X(){
        return _x;
    }
    int Y(){
        return _y;
    }
    char Oper(){
        return _operator;
    }
    void SetValue(int x, int y, char oper){
        _x = x;
        _y = y;
        _operator = oper;
    }

private:
    int _x; // 左操作数
    int _y; // 右操作数
    char _operator; // 运算符
};

// 服务端返回应答
class Response
{
public:
    Response(int result = -1, int exit_code = 0, std::string exit_info = "Success")
        : _result(result), _exit_code(exit_code), _exit_info(exit_info)
    {
    }

    // 序列化:结构体成员 -》 json串
    bool Serialize(std::string *out_jsonstr)
    {
#ifdef FLAG
        *out_jsonstr = std::to_string(_result) + self_cut_str + std::to_string(_exit_code) + self_cut_str + _exit_info;
        return true;
#else
        Json::Value root;
        root["result"] = _result;
        root["exit_code"] = _exit_code;
        root["exit_info"] = _exit_info;
        Json::FastWriter writer;
        *out_jsonstr = writer.write(root);
        return true;
#endif
    }

    // version2 自定义序列串报文:"len\r\nresult#exit_code#info\r\n"
    // 反序列化:json串 -》 结构体成员
    bool DeSerialize(const std::string &in_jsonstr)
    {
#ifdef FLAG
        auto left = in_jsonstr.find(self_cut_str);
        if(left == std::string::npos) return false;
        auto right = in_jsonstr.rfind(self_cut_str);
        if(right == std::string::npos) return false;
        if(left + 1 + self_cut_str.size() != right) return false;
        _result = std::stoi(in_jsonstr.substr(0, left));
        _exit_code = in_jsonstr[right - 1] -'\0';
        _exit_info = in_jsonstr.substr(right + self_cut_str.size());
        return true;
#else
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(in_jsonstr, root);
        _result = root["result"].asInt();
        _exit_code = root["exit_code"].asInt();
        _exit_info = root["exit_info"].asString();
        return res;
#endif
    }

    void PrintResult()
    {
        std::cout << "result: " << _result << ", code: " << _exit_code << ", desc: " << _exit_info << std::endl;
    }

public:
    int _result; // 计算结果
    int _exit_code; // 退出码
    std::string _exit_info; // 退出码对应的退出信息
};


// 工厂模式,提供构造对象实例的函数方法
class FactoryForRequestAndReponse
{
public:
    static std::shared_ptr<Request> BulidRequestObject()
    {
        return std::make_shared<Request>();
    }

    static std::shared_ptr<Response> BulidResponseObject()
    {
        return std::make_shared<Response>();
    }
};

NetCol.hpp : 计算器头文件,其中提供了将请求对象转化为应答对象的方法。

#pragma once

#include "Protocol.hpp"
#include <memory>

class NetCal
{
public:
    std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req)
    {
        auto resp = FactoryForRequestAndReponse::BulidResponseObject();
        switch (req->Oper())
        {
        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->_exit_code = 1;
                resp->_exit_info = "div zero";
            }
            else
            {
                resp->_result = req->X() / req->Y();
            }
        }
        break;
        case '%':
        {
            if (req->Y() == 0)
            {
                resp->_exit_code = 2;
                resp->_exit_info = "mod zero";
            }
            else
            {
                resp->_result = req->X() % req->Y();
            }
        }
        break;
        default:
        {
            resp->_exit_code = 3;
            resp->_exit_info = "illegal operation";
        }
        break;
        }
        return resp;
    }
};

 Service.hpp : I/O服务类,其中包含了服务端进行网络通信的方法。作为服务端类的参数构造服务器对象,目的是实现服务器自身启动功能与通信功能方法的解耦。通过传递不同的参数,可以使服务器具有对通信信息不同的处理能力。

#pragma once
#include "NetCol.hpp"
#include "Protocol.hpp"
#include "Socket.hpp"
#include <functional>

// 服务端需要的任务函数的类型
// using Tcp_Server_FuncType = std::function<void(SockSPtr, InetAddr)>; // 注:InetAddr 为类,该对象中包含目标端的IP地址、端口号

// IO_Service 类用于处理服务端对客户端信息的接收和应答工作, 内部由外层提供 将 请求 转变为 应答 的功能函数
using Process_Request_and_Return_Response_t = std::function<std::shared_ptr<Response>(std::shared_ptr<Request>)>;

class IO_Service
{
private:
    Process_Request_and_Return_Response_t _process;

public:
    IO_Service(Process_Request_and_Return_Response_t process)
        : _process(process)
    {
    }

    void IOExecute(SockSPtr io_socksptr, InetAddr from_client_inetaddr)
    {
        std::string message;
        while (true)
        {
            // 1、接收来自客户端的请求报文
            if(io_socksptr->Recv(&message) < 0)
            {
                LOG(FATAL, "Recv fail!");
            }

            // 2、将接收到的请求进行解码操作,获取json串
            std::string json_str = RemoveCode(message);
            // 如果没有提取到完整的json串,说明服务端当前接收到的报文不完整,继续进行recv操作
            if(json_str.empty()){
                continue;
            }
            std::cout << json_str << std::endl; 
            // 3、将json串反序列化为请求
            std::shared_ptr<Request> request = std::make_shared<Request>();
            if(!request->DeSerialize(json_str))
            {
                LOG(FATAL, "request->DeSerialize fail!");
            }

            // 4、处理请求,返回应答结果;构建应答,接收应答结果
            std::shared_ptr<Response> response = _process(request);

            // 5、将应答进行序列化
            std::string return_json_str;
            if(!response->Serialize(&return_json_str))
            {
                LOG(FATAL, "response->Serialize fail!");
            }
            // 将已经序列化的应答做成完整报文,发送给客户端
            // 1、对应答序列化后的json串加码
            return_json_str = AddCode(return_json_str);
            // 2、将报文发送给客户端
            if(io_socksptr->Send(return_json_str) < 0)
            {
                LOG(FATAL, "Send fail!");
            }
        }
    }
};

TCP_Server.hpp:服务端类

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <arpa/inet.h>
#include <cstring>
#include <signal.h>
#include <sys/wait.h>
#include "ThreadPool.hpp"
#include <functional>
#include "Socket.hpp"

static uint16_t gport = 8888;

static const int MAX_LEN = 5;
static const int BUFFER_SIZE = 256;

using Tcp_Server_FuncType = std::function<void(SockSPtr, InetAddr)>; // 注:InetAddr 为类,该对象中包含目标端的IP地址、端口号

class Tcp_Server
{
private:
    SockSPtr _listen_sockfd; // 监听套接字,使用listen函数设置为监听态,负责监听来自客户端的连接请求
    bool _is_running;
    uint16_t _port;
    Tcp_Server_FuncType _tcp_service; // 服务端需要执行的任务对象
public:
    // 构造函数,提供端口号
    Tcp_Server(Tcp_Server_FuncType tcp_service, uint16_t port = gport)
        : _port(port), _listen_sockfd(std::make_shared<Tcp_Socket>()), _is_running(false), _tcp_service(tcp_service)
    {
    }

    void InitServer()
    {
        
        // 1、 创建监听套接字
        // 2、绑定本地ip地址和port端口号
        // 3、将套接字设置为【监听状态】, 以监听来自客户端的连接请求
        _listen_sockfd->BuildListenSocket(_port);
    }


    // 运行服务端
    void Loop()
    {
        // 多线程版本
        _is_running = true;
        while(_is_running)
        {
            // 1、获取来自客户端的连接请求,并获得I/O专用套接字
            InetAddr from_client;
            SockSPtr _io_sockfd = _listen_sockfd->Accepter(&from_client);

            pthread_t tid = 0;
            ThreadData * thread_data = new ThreadData(_io_sockfd, this, from_client);
            //  线程需要执行类中的Service函数,同时主线程不能对该线程进行等待回收,所以需要该线程进行线程分离,让线程退出后自动由系统回收
            if(pthread_create(&tid, nullptr, ThreadRoute, thread_data) < 0)
            {
                LOG(FATAL, "Thread Create False!");
                exit(-1);
            }
            
        }
    }

    // 为什么要单独设置一个线程数据类呢?
    // 如果在Tcp_Server类中设置一个静态方法,该方法无法访问类中的非静态成员。当然,将Tcp_Server类对象本身的指针作为线程函数的参数传递给线程执行函数也是可以的
    // 但是,服务器类对象中包含的成员变量和方法或许会非常多,而线程执行函数仅需执行IO工作和对信息的处理工作,并不需要这些数据。所以我们单独设计一个内部类,
    // 在该类中添加所需的成员变量即可
    class ThreadData
    {
    public:
        SockSPtr _io_sockfd; // 进行io通信的套接字描述符
        Tcp_Server* _self; // Tcp_Server类指针,用于调取该类中的函数方法
        InetAddr _net_addr; // ip + port
    public:
        ThreadData(SockSPtr io_sockfd, Tcp_Server* self, InetAddr net_addr)
        : _io_sockfd(io_sockfd), _self(self), _net_addr(net_addr)
        {}
    };

    // 
    static void* ThreadRoute(void* thread_data)
    {
        // 1、将该线程设置为分离态,该线程运行结束后系统自动回收资源
        pthread_detach(pthread_self());
        // 2、运行任务函数
        ThreadData* thread_self_data = static_cast<ThreadData*>(thread_data);
        thread_self_data->_self->_tcp_service(thread_self_data->_io_sockfd, thread_self_data->_net_addr);
        close(thread_self_data->_io_sockfd->Sockfd());
        delete thread_self_data;
        return nullptr;
    }
};

TCP_Server_main.cc:服务端运行逻辑 

#include "Tcp_Server_New.hpp"
#include "Service.hpp"
#include "NetCol.hpp"
//在命令行需自主输入绑定的端口号
int main(int argc, char* argv[])
{
    if(argc < 2){
        std::cout << "未输入端口号..." << std::endl;
        exit(-1);
    }
    uint16_t port = std::stoi(argv[1]);
    NetCal net_calculator;
    // 为服务类绑定 请求-》应答 方法
    IO_Service service(std::bind(&NetCal::Calculator, &net_calculator, std::placeholders::_1));
    // 绑定命令类中的命令处理方法,作为服务端的执行函数构造服务端
    Tcp_Server server(std::bind(&IO_Service::IOExecute, &service, std::placeholders::_1, std::placeholders::_2), port);
    server.InitServer(); // 初始化服务端
    server.Loop(); // 启动服务端
    return 0;
}

TCP_Client.hpp:客户端类

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <arpa/inet.h>
#include <cstring>
#include <signal.h>
#include <sys/wait.h>
#include "Log.hpp"
#include "Socket.hpp"
#include "Protocol.hpp"

static const int BUFFER_SIZE = 256;
const std::string opers = "+-*/%&^!";
class Tcp_Client
{
private:
    SockSPtr _sockfd;
    std::string _to_server_ip;
    uint16_t _to_server_port;
    bool _is_running;

public:
    Tcp_Client(const std::string &ip, const uint16_t port)
        : _to_server_ip(ip), _to_server_port(port), _sockfd(std::make_shared<Tcp_Socket>())
    {
    }
    ~Tcp_Client()
    {
        _sockfd->Close();
    }

    void InitClient()
    {
        // 客户端无需手动绑定bind,在使用connect函数连接时,自动绑定IP地址和端口号
        // 1、创建套接字,连接服务端
        if (!_sockfd->BuildClientSocket(_to_server_ip, _to_server_port))
        {
            LOG(FATAL, "BuildClientSocket fail!!!");
        }
    }

    // 启动客户端
    void Start()
    {
        _is_running = true;
        while (_is_running)
        {
            // // 1、输入需要计算的数字和操作符
            // int x, y;
            // char oper;
            // std::cout << "Please Enter X # ";
            // std::cin >> x;
            // std::cout << "Please Enter Operator # ";
            // std::cin >> oper;
            // std::cout << "Please Enter Y # ";
            // std::cin >> y;

            // 构建数据
            int x = rand() % 10;
            usleep(x * 1000);
            int y = rand() % 10;
            usleep(x * y * 100);
            char oper = opers[y % opers.size()];
            // 2、根据输入信息创建请求
            std::shared_ptr<Request> resquest = std::make_shared<Request>(x, y, oper);
            // 3、将请求序列化,获得序列化后的json串
            std::string json_str;
            if (!resquest->Serialize(&json_str))
            {
                LOG(FATAL, "request->DeSerialize fail!");
            }
            std::cout << std::endl << json_str;
            // 4、为序列化后的json串加码,生成报文
            json_str = AddCode(json_str);

            // 5、将报文发送给服务端
            if (_sockfd->Send(json_str) < 0)
            {
                LOG(FATAL, "Client Write To Server False!\n");
                exit(-1);
            }
            LOG(INFO, "Client Write To Server Success!\n");

            // 保证读取至少一条完整的报文
            while (true)
            {
                // 6、接受来自服务端的应答
                std::string from_client_message;
                if (_sockfd->Recv(&from_client_message) < 0)
                {
                    LOG(FATAL, "Client Write To Server False!\n");
                    break;
                }

                // 7、对报文解码获取json串
                std::string from_client_json_str = RemoveCode(from_client_message);
                if (from_client_json_str.empty())
                {
                    continue;
                }
                // 8、根据json串构建应答
                std::shared_ptr<Response> response = std::make_shared<Response>();
                response->DeSerialize(from_client_json_str);

                // 9、打印应答结果
                response->PrintResult();
                break;
            }
            sleep(1);
        }
        _is_running = false;
    }
};

TCP_Client_main.cc:客户端运行逻辑 

#include "Tcp_Client.hpp"

//在命令行需自主输入目标客户端的IP地址和需要绑定的端口号
int main(int argc, char* argv[])
{
    if(argc < 3){
        std::cout << "命令行参数过少..." << std::endl;
        exit(-1);
    }
    // 构造服务端
    Tcp_Client client(argv[1], std::stoi(argv[2]));
    client.InitClient(); // 构造客户端
    client.Start(); // 启动客户端
    return 0;
}

通过上述代码我们可以理解:协议实际上就是通信双方必须遵守提前约定好通信格式、信息的处理方式、错误检测与纠正机制、数据传输的顺序等方面的细节。

例如:在HTTP协议中,客户端和服务器之间的通信就是通过提前约定好的格式进行的。客户端发送一个HTTP请求,服务器根据请求的内容返回相应的HTTP响应。HTTP协议规定了请求和响应的格式、状态码的含义、头部的字段等等,这就是一种协议。

通过协议,通信双方可以在不明确各自内部实现细节的情况下,依然能够有效地进行数据交换和信息处理,确保通信的准确性和可靠性。


http://www.kler.cn/a/372747.html

相关文章:

  • 编写dockerfile生成镜像,并且构建容器运行
  • C# 企业微信机器人推送消息 windows服务应用程序的使用
  • 【Linux网络】UdpSocket
  • 详细解读 CVPR2024:VideoBooth: Diffusion-based Video Generation with Image Prompts
  • SD教程 重绘 ControlNet-Inpain
  • Javascript实现的网页版绘图板
  • 【Web前端】JavaScript 对象原型与继承机制
  • 「C/C++」C++ 三大特性 之 类和对象
  • 版本管理工具切换 | svn切换到gitlab | gitblit 迁移到 gitlab
  • STL——list的介绍和使用
  • 微信小程序-全局数据共享/页面间通信
  • unity :Error building Player: Incompatible color space with graphics API
  • k8s Ingress 七层负载
  • 迪杰斯特拉算法(Dijkstra‘s Algorithm
  • 路由参数与请求方式
  • 理解环境变量与Shell编程:Linux开发的基础
  • 将你的 Kibana Dev Console 请求导出到 Python 和 JavaScript 代码
  • GB/T 28046.2-2019 道路车辆 电气及电子设备的环境条件和试验 第2部分:电气负荷(4)
  • 如何写好prompt以及评测prompt的好坏
  • 14.社团管理系统(基于springboot和vue)
  • 力扣hot100-->递归/回溯
  • 10.Three.js射线拾取,实现点击选中场景中的物体
  • 【人工智能】重塑未来生活与工作的引擎
  • 鸿蒙原生应用开发及部署:首选华为云,开启HarmonyOS NEXT App新纪元
  • 【每日C/C++问题】
  • 国内短剧源码短剧系统搭建小程序部署H5、APP打造短剧平台