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

news2024/11/5 16:41:00

目录

一、什么是协议?

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

三、协议的应用

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

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

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

五、序列化推荐格式之一: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.coloradmin.cn/o/2231462.html

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

相关文章

基于MATLAB的加噪语音信号的滤波

一&#xff0e;滤波器的简述 在MATLAB环境下IIR数字滤波器和FIR数字滤波器的设计方 法即实现方法&#xff0c;并进行图形用户界面设计&#xff0c;以显示所介绍迷你滤波器的设计特性。 在无线脉冲响应&#xff08;IIR&#xff09;数字滤波器设计中&#xff0c;先进行模拟滤波器…

Java项目实战II基于Spring Boot的智能家居系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 随着物联网技术的快速发展和普及&#…

基于Spring Boot+Vue的助农销售平台(协同过滤算法、限流算法、支付宝沙盒支付、实时聊天、图形化分析)

&#x1f388;系统亮点&#xff1a;协同过滤算法、节流算法、支付宝沙盒支付、图形化分析、实时聊天&#xff1b; 一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk1…

【C++笔记】容器适配器及deque和仿函数

【C笔记】容器适配器及deque和仿函数 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】容器适配器及deque和仿函数前言一.容器适配器1.1什么是容器适配器1.2 STL标准库中stack和queue的底层结构 二.stack2.1stack类模…

软考:中间件

中间件 中间件是一类位于操作系统软件与用户应用软件之间的计算机软件&#xff0c;它包括一组服务&#xff0c;以便于运行在一台或多台机器上的多个软件通过网络进行交互。 中间件的主要功能包括通信支持和应用支持。 通信支持为应用软件提供平台化的运行环境&#xff0c;屏蔽…

统信UOS设备驱动开发-常见问题

包含linux设备驱动开发的基础知识及统信UOS设备驱动的总体架构,常用的设备驱动开发调试优化手段及在环境搭建和代码编写过程中常见问题处理等。 文章目录 环境搭建如何编译驱动代码编写如何实现同源异构环境搭建 如何编译内核 下载并解压内核源码包,进入源码根目录,内核的编…

JS 异步 Promise、Async、await详解

目录 一、JS里的同步异步 二、Promise 1、状态 2、all()、race()、any() 3、简单案例 4、异步执行案例 5、解决异步嵌套繁琐的场景 三、async和await 1、async返回类型 2、async与await结合使用的简单案例 3、解决异步嵌套问题 4、批量请求优化 一、JS里的同步异步…

【Vue3】Vue3相比Vue2有哪些新特性?全面解析与应用指南

&#x1f9d1;‍&#x1f4bc; 一名茫茫大海中沉浮的小小程序员&#x1f36c; &#x1f449; 你的一键四连 (关注 点赞收藏评论)是我更新的最大动力❤️&#xff01; &#x1f4d1; 目录 &#x1f53d; 前言1️⃣ 响应式系统的改进2️⃣ Composition API的引入3️⃣ 更好的Type…

Vue 事件阻止 e.preventDefault();click.prevent

Vue 事件阻止 Vue 事件阻止 e.preventDefault(); click.prevent修饰符

基于vue3和elementPlus的el-tree组件,实现树结构穿梭框,支持数据回显和懒加载

一、功能 功能描述 数据双向穿梭&#xff1a;支持从左侧向右侧转移数据&#xff0c;以及从右侧向左侧转移数据。懒加载支持&#xff1a;支持懒加载数据&#xff0c;适用于大数据量的情况。多种展示形式&#xff1a;右侧列表支持以树形结构或列表形式展示。全选与反选&#xf…

leetcode-21-合并两个有序链表

题解&#xff1a; 1、初始化哑节点dum 2、 3、 代码&#xff1a; 参考&#xff1a;leetcode-88-合并两个有序数组

WPF怎么通过RestSharp向后端发请求

1.下载RestSharpNuGet包 2.请求类和响应类 public class ApiRequest {/// <summary>/// 请求地址/// </summary>public string Route { get; set; }/// <summary>/// 请求方式/// </summary>public Method Method { get; set; }/// <summary>//…

指派问题的求解

实验类型&#xff1a;◆验证性实验 ◇综合性实验 ◇设计性实验 实验目的&#xff1a;学会使用Matlab求解指派问题。 实验内容&#xff1a;利用Matlab编程实现枚举法求解指派问题。 实验例题&#xff1a;有5人分别对应完成5项工作&#xff0c;其各自的耗费如下表所示&#…

vue3 gsap 基于侦听器的动画

1、gsap实现动画 https://gsap.com/ .以上来自baidu ai 2、代码&#xff1a; 安装gsap&#xff1a;pnpm install gsap <script setup> import { ref, reactive, watch } from vue import gsap from gsapconst number ref(0) const tweened reactive({number: 0 })wat…

Flutter CustomScrollView 效果-顶栏透明与标签栏吸顶

CustomScrollView 效果 1. 关键组件 CustomScrollView, SliverOverlapAbsorber, SliverPersistentHeader 2. 关键内容 TLDR SliverOverlapAbsorber 包住 pinned为 true 的组件 可以被CustomScrollView 忽略高度。 以下的全部内容的都为了阐述上面这句话。初阶 Flutter 开发知…

江协科技STM32学习- P29 实验- 串口收发HEX数据包/文本数据包

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

4.1 进程管理

在异步通信中&#xff0c;每个字符包含1位起始位、7位数据位和2位终止位&#xff0c;若每秒钟传送500个字符&#xff0c;则有效数据速率为&#xff08; &#xff09;。 A. 500b/s B. 700b/s C. 3500b/s D. 5000b/s 正确答案是 C。 解析 本题考查异步传输协议基础知识。 根据题目…

[进阶]集合的进阶(1)泛型

文章目录 泛型的深入泛型的细节泛型可以在很多地方定义泛型的继承和通配符总结 泛型的深入 泛型:是JDK5中引入的特性&#xff0c;可以在编译阶段约束操作的数据类型&#xff0c;并进行检查 泛型的格式:<数据类型> 注意:泛型只能引用数据类型 泛型的好处 统一了数据类型…

GB/T 28046.3-2011 道路车辆 电气及电子设备的环境条件和试验 第3部分:机械负荷(4)

写在前面 本系列文章主要讲解道路车辆电气及电子设备的环境条件和试验GB/T 28046标准的相关知识&#xff0c;希望能帮助更多的同学认识和了解GB/T 28046标准。 若有相关问题&#xff0c;欢迎评论沟通&#xff0c;共同进步。(*^▽^*) 第3部分&#xff1a;机械负荷 4.1 振动 …

【案例】旗帜飘动

开发平台&#xff1a;Unity 6.0 开发工具&#xff1a;Shader Graph 参考视频&#xff1a;Unity Shader Graph 旗帜飘动特效   一、效果图 二、Shader Graph 路线图 三、案例分析 核心思路&#xff1a;顶点偏移计算 与 顶点偏移忽略 3.1 纹理偏移 视觉上让旗帜保持动态飘动&a…