【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;
#elseJson::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 FLAGauto 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;
#elseJson::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;
#elseJson::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 FLAGauto 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;
#elseJson::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 + portpublic: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.rhkb.cn/news/463292.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ZYNQ7045之YOLO部署——FPGA-ZYNQ Soc实战笔记1

一、简介 1、目标检测概念 2、目标检测应用 3、目标检测发展历程 二、YOLO V1 1、输入 必须为448x448分辨率 2、网络结构 卷积 池化 卷积 池化 3、输出 最终7x7x30表示&#xff0c;7x7个各自&#xff0c;每个格子有30个数据&#xff0c;30个数据包含两个部分 1&#xff1a;…

服务器数据恢复—SAN环境中LUN映射错误导致文件系统一致性出错的数据恢复案例

服务器数据恢复环境&#xff1a; SAN光纤网络环境&#xff0c;存储由一组6块硬盘组建的RAID6阵列构成&#xff0c;划分为若干LUN&#xff0c;MAP到跑不同业务的SUN SOLARIS操作系统服务器上。 服务器故障&分析&#xff1a; 因为业务需要&#xff0c;用户在该光纤存储环境中…

聪明的你能从千门八将108局学到什么,对你的未来人生有哪些深远的影响?

千门八将108局&#xff1a;智慧的启迪与人生指引 在古老智慧的宝库中&#xff0c;千门八将108局犹如璀璨星辰&#xff0c;闪耀着神秘而深邃的光芒。那些认真钻研过这些局的人&#xff0c;仿佛经历了一场穿越时空的智慧洗礼&#xff0c;从中收获了无价的人生财富。 一、从千门八…

Launcher3 去掉桌面搜索索框

文章目录 需求实现需求说明 参考资料修改文件实现思路首页显示的搜索框去除应用列表中的搜索框去除解决方案代码跟踪代码扩展 需求 Launcher3 去掉搜桌面索框 实现需求说明 每个平台平台源码有区别&#xff0c;比如&#xff1a;MTK、高通、展讯、RK、谷歌…单个平台下 不同A…

qt QDoubleSpinBox详解

1、概述 QDoubleSpinBox是Qt框架中的一个控件&#xff0c;专门用于浮点数&#xff08;即小数&#xff09;的输入和调节。它提供了一个用户界面元素&#xff0c;允许用户在预设的范围内通过拖动滑块、点击箭头或使用键盘来递增或递减浮点数值。QDoubleSpinBox通常用于需要精确数…

NVR小程序接入平台/设备EasyNVR多个NVR同时管理视频监控新选择

在数字化转型的浪潮中&#xff0c;视频监控作为安防领域的核心组成部分&#xff0c;正经历着前所未有的技术革新。随着技术的不断进步和应用场景的不断拓展&#xff0c;视频监控系统的兼容性、稳定性以及安全性成为了用户关注的焦点。NVR小程序接入平台/设备EasyNVR&#xff0c…

qt QAction详解

1、概述 QAction是Qt框架中的一个抽象类&#xff0c;用于表示用户界面中的一个动作&#xff08;action&#xff09;。这些动作可以绑定到菜单项、工具栏按钮或快捷键上&#xff0c;提供了一种灵活的方式来处理用户交互。QAction不仅包含了动作的名称、图标、提示信息等属性&am…

【快速上手】pyspark 集群环境下的搭建(Standalone模式)

目录 前言 &#xff1a; 一、spark运行的五种模式 二、 安装步骤 安装前准备 1.第一步&#xff1a;安装python 2.第二步&#xff1a;在bigdata01上安装spark 3.第三步&#xff1a;同步bigdata01中的spark到bigdata02和03上 三、集群启动/关闭 四、打开监控界面验证 前…

python如何调字体大小

1、打开电脑上的IDLE程序。 2、默认字体大小给出一个直观的展示。小编自认为还是比较小的&#xff0c;觉得调整大一点比较好。 3、点击菜单栏的【Options】。 4、然后点击【Configure IDLE】。 5、默认字体是新宋体&#xff0c;大小是size4。 6、根据自己需要设置字体大小后&am…

创造、竞争、征服:成为 The Sandbox 的创作者

Alpha 第 4 季将改变创作者在 The Sandbox 中实现激情的方式&#xff01;本季有 40% 的体验由我们的社区打造&#xff0c;The Sandbox 是你打造难忘冒险的平台&#xff0c;并在元宇宙中激励他人。无论你的梦想是制作惊险刺激的任务&#xff0c;还是设计自己生机勃勃的风景&…

Nico,从零开始干掉Appium,移动端自动化测试框架实现

开头先让我碎碎念一波~去年差不多时间发布了一篇《 UiAutomator Nico&#xff0c;一个基于纯 adb 命令实现的安卓自动化测试框》&#xff08;https://testerhome.com/topics/37042&#xff09;&#xff0c; 由于种种原因 (详见此篇帖子) 当时选择了用纯 adb 命令来实现安卓自动…

Vue项目开发:Vuex使用,表单验证配置,ESLint关闭与常见问题解决方案

文章目录 vuexvue配置form表单验证移除vue中表单验证的两种方法关闭vue项目的eslint代码校验做vue项目出现的问题 vuex Vue提供的状态管理工具&#xff0c;用于统一管理我们项目中各种数据的交互和重用&#xff0c;存储我们需要用到的数据对象属性 state&#xff1a;vuex的基本…

波尼音乐 2.3.0-b1 | 开源免费的音乐播放器,附两个公共接口

波尼音乐最初作为一个毕设项目&#xff0c;凭借其实现了本地与网络音乐播放的能力而受到许多用户的喜爱。随着百度在线音乐API的关闭&#xff0c;波尼音乐逐渐失去在线音乐播放功能。在开源社区的支持下&#xff0c;开发者发现新的网易云音乐API&#xff0c;重启项目并进行全面…

“死鱼眼”,不存在的,一个提词小技巧,拯救的眼神——将内容说给用户,而非读给用户!

视频录制时&#xff0c;死鱼眼问题常见 即便内容再好&#xff0c;眼神死板也会减分 痛点真痛&#xff1a;拍视频时容易紧张 面对镜头&#xff0c;许多人难免紧张 神情僵硬&#xff0c;眼神无光&#xff0c;甚至忘词 这不仅影响表现&#xff0c;还让人难以专注 忘我场景&#x…

Java | Leetcode Java题解之第525题连续数组

题目&#xff1a; 题解&#xff1a; class Solution {public int findMaxLength(int[] nums) {int maxLength 0;Map<Integer, Integer> map new HashMap<Integer, Integer>();int counter 0;map.put(counter, -1);int n nums.length;for (int i 0; i < n;…

C语言 | Leetcode C语言题解之第526题优美的排列

题目&#xff1a; 题解&#xff1a; int countArrangement(int n) {int f[1 << n];memset(f, 0, sizeof(f));f[0] 1;for (int mask 1; mask < (1 << n); mask) {int num __builtin_popcount(mask);for (int i 0; i < n; i) {if (mask & (1 <<…

HarmonyOS第一课 06 构建更加丰富的页面-习题解析

判断题 1. Tabs组件可以通过接口传入一个TabsController&#xff0c;该TabsController可以控制Tabs组件进行页签切换。T 正确(True) 错误(False) 使用 this.tabsController.changeIndex(this.currentIndex); 可以切换页签 WebviewController提供了变更Web组件显示内容的接口…

xilinx vitis 更换硬件平台——ZYNQ学习笔记5

1、重新生成硬件信息 2、选择带有bit信息 3、设施路径和名字 4、打开更新硬件选项 5、选择新的硬件信息 6、打开系统工程界面 7、复位硬件信息 更新完毕

哪一款防脱生发的产品效果好?教科书式教你如何挑

头发护理越来越被重视&#xff0c;因为现在脱发秃头的人实在太多了&#xff0c;本人几年前就开始关注头发护理了&#xff0c;目前头发光泽垂顺浓密&#xff0c;好多次被夸发质好发量多。今天给大家推荐几款好用的防脱育发精华吧&#xff0c;好用有效无平替版。 第1款&#xff…

一键AI换衣-可图AI试衣

我们的真的实现了穿衣自由了吗&#xff1f;上传一张人物图片和衣服的图片&#xff0c;就能实现一键换衣。 这就是可图AI试衣项目 魔塔地址&#xff1a;https://www.modelscope.cn/studio ... lors-Virtual-Try-On 参考&#xff1a; 一键AI换衣-可图AI试衣 https://www.jinsh…