中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

可以做mc圖片的網(wǎng)站外鏈推廣

可以做mc圖片的網(wǎng)站,外鏈推廣,圖片文字制作,做網(wǎng)站客戶不給錢怎么辦目錄 再談 "協(xié)議" 結(jié)構(gòu)化數(shù)據(jù)的傳輸 序列化和反序列化 網(wǎng)絡(luò)版計(jì)算器 封裝套接字操作 服務(wù)端代碼 服務(wù)進(jìn)程執(zhí)行例程 啟動(dòng)網(wǎng)絡(luò)版服務(wù)端 協(xié)議定制 客戶端代碼 代碼測(cè)試 使用JSON進(jìn)行序列化與反序列化 我們程序員寫的一個(gè)個(gè)解決我們實(shí)際問(wèn)題,滿…

目錄

再談 "協(xié)議"

結(jié)構(gòu)化數(shù)據(jù)的傳輸

序列化和反序列化

網(wǎng)絡(luò)版計(jì)算器?

封裝套接字操作?

服務(wù)端代碼

服務(wù)進(jìn)程執(zhí)行例程

啟動(dòng)網(wǎng)絡(luò)版服務(wù)端

協(xié)議定制

客戶端代碼

代碼測(cè)試

使用JSON進(jìn)行序列化與反序列化


我們程序員寫的一個(gè)個(gè)解決我們實(shí)際問(wèn)題,滿足我們?nèi)粘P枨蟮木W(wǎng)絡(luò)程序,都是在應(yīng)用層。

再談 "協(xié)議"

我們之前講過(guò):協(xié)議是一種 "約定"。網(wǎng)絡(luò)協(xié)議是通信計(jì)算機(jī)雙方必須共同遵從的一組約定,只有通信計(jì)算機(jī)雙方都遵守相同的協(xié)議,計(jì)算機(jī)之間才能互相通信交流。

結(jié)構(gòu)化數(shù)據(jù)的傳輸

socket api的接口,在讀寫數(shù)據(jù)時(shí),都是按 "字符串" 的方式來(lái)發(fā)送接收的。

  • 如果需要傳輸?shù)臄?shù)據(jù)是一個(gè)字符串,那么直接將這一個(gè)字符串發(fā)送到網(wǎng)絡(luò)當(dāng)中,此時(shí)對(duì)端也能從網(wǎng)絡(luò)當(dāng)中獲取到這個(gè)字符串。
  • 但如果需要傳輸?shù)氖且恍┙Y(jié)構(gòu)化的數(shù)據(jù),此時(shí)就不能將這些數(shù)據(jù)一個(gè)個(gè)發(fā)送到網(wǎng)絡(luò)當(dāng)中。?

那么如果我們要傳輸一些"結(jié)構(gòu)化的數(shù)據(jù)" 怎么辦呢?

例如,我們需要實(shí)現(xiàn)一個(gè)服務(wù)器版的加法器。我們需要客戶端把要計(jì)算的兩個(gè)加數(shù)發(fā)過(guò)去,然后由服務(wù)器進(jìn)行計(jì)算,最后再把結(jié)果返回給客戶端。那么客戶端每次給服務(wù)端發(fā)送的請(qǐng)求數(shù)據(jù)當(dāng)中,就需要包括左操作數(shù)、右操作數(shù)以及對(duì)應(yīng)需要進(jìn)行的操作,此時(shí)客戶端要發(fā)送的就不是一個(gè)簡(jiǎn)單的字符串,而是一組結(jié)構(gòu)化的數(shù)據(jù)。

當(dāng)客戶端選擇將結(jié)構(gòu)化的數(shù)據(jù)逐一發(fā)送到網(wǎng)絡(luò)中,服務(wù)端接收數(shù)據(jù)的過(guò)程也會(huì)相應(yīng)地碎片化。每次從網(wǎng)絡(luò)中接收到一部分?jǐn)?shù)據(jù),服務(wù)端都需要對(duì)這些離散的信息進(jìn)行整理,嘗試將它們重新組合成原始的結(jié)構(gòu)化數(shù)據(jù)。這個(gè)過(guò)程既復(fù)雜又容易出錯(cuò),因?yàn)閿?shù)據(jù)可能在傳輸過(guò)程中出現(xiàn)丟失或順序混亂的情況。

因此,為了簡(jiǎn)化數(shù)據(jù)傳輸和處理的流程,客戶端通常會(huì)采取“打包”策略。打包意味著將多個(gè)相關(guān)的數(shù)據(jù)元素組合成一個(gè)整體,然后再進(jìn)行傳輸。這樣,服務(wù)端每次從網(wǎng)絡(luò)中接收到的都是一個(gè)完整的數(shù)據(jù)包,其中包含了所有必要的信息。

客戶端常見的“打包”方式主要有兩種:

約定方案一:

  • 客戶端發(fā)送一個(gè)形如"1+1"的字符串;
  • 這個(gè)字符串中有兩個(gè)操作數(shù), 都是整形;
  • 兩個(gè)數(shù)字之間會(huì)有一個(gè)字符是運(yùn)算符, 運(yùn)算符只能是 + ;
  • 數(shù)字和運(yùn)算符之間沒(méi)有空格;

客戶端能夠?qū)⒔Y(jié)構(gòu)化的數(shù)據(jù)編排成一個(gè)字符串格式,并通過(guò)網(wǎng)絡(luò)將其發(fā)送出去。當(dāng)服務(wù)端從網(wǎng)絡(luò)接收到這個(gè)字符串時(shí),它會(huì)采用與客戶端相同的解析方法,從而從這個(gè)字符串中提取出原始的結(jié)構(gòu)化數(shù)據(jù)。這樣的通信方式確保了數(shù)據(jù)的完整性和準(zhǔn)確性在客戶端和服務(wù)端之間的傳輸。

約定方案二:

  • 定義結(jié)構(gòu)體來(lái)表示我們需要交互的信息;
  • 發(fā)送數(shù)據(jù)時(shí)將這個(gè)結(jié)構(gòu)體按照一個(gè)規(guī)則轉(zhuǎn)換成字符串,接收到數(shù)據(jù)的時(shí)候再按照相同的規(guī)則把字符串轉(zhuǎn)化回結(jié)構(gòu)體;
  • 這個(gè)過(guò)程叫做 "序列化" 和?“反序列化”

客戶端可以設(shè)計(jì)一個(gè)特定的結(jié)構(gòu)體,將需要交互的信息定義到這個(gè)結(jié)構(gòu)體當(dāng)中。在發(fā)送數(shù)據(jù)前,客戶端會(huì)利用序列化技術(shù)將這個(gè)數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換成一種統(tǒng)一的、可傳輸?shù)淖址蜃止?jié)流格式。當(dāng)服務(wù)端接收到這些數(shù)據(jù)后,它會(huì)利用反序列化技術(shù)將這個(gè)字符串或字節(jié)流還原成原始的數(shù)據(jù)結(jié)構(gòu)。通過(guò)這種方式,服務(wù)端可以輕松地提取出客戶端發(fā)送的信息,并進(jìn)行相應(yīng)的處理。這種序列化和反序列化的過(guò)程確保了數(shù)據(jù)在不同系統(tǒng)間的兼容性和可交換性。?

序列化和反序列化

  • 序列化是將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问?#xff08;字節(jié)序列)的過(guò)程。
  • 反序列化是把字節(jié)序列恢復(fù)為對(duì)象的過(guò)程。

OSI七層模型中的表示層的主要任務(wù)是將設(shè)備內(nèi)部特有的數(shù)據(jù)格式,即應(yīng)用層上的數(shù)據(jù)格式,轉(zhuǎn)換為符合網(wǎng)絡(luò)傳輸標(biāo)準(zhǔn)的格式。這種網(wǎng)絡(luò)標(biāo)準(zhǔn)數(shù)據(jù)格式通常是通過(guò)序列化過(guò)程得到的,使得數(shù)據(jù)能夠以一致和可理解的方式在網(wǎng)絡(luò)中進(jìn)行傳輸。

網(wǎng)絡(luò)版計(jì)算器?

封裝套接字操作?

由于服務(wù)端和客戶端都需要?jiǎng)?chuàng)建套接字,以及使用套接字完成一些固定的操作,因此我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的TCP套接字(socket)類的實(shí)現(xiàn),它封裝了套接字的基本操作:包括創(chuàng)建、綁定、監(jiān)聽、接受連接和連接。這樣服務(wù)端和客戶端都可以直接調(diào)用這些函數(shù)。封裝套接字操作可以使服務(wù)端和客戶端代碼更整潔、可重用,并減少重復(fù)代碼。

Socket.hpp

#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"enum{SocketErr = 2,BindErr,ListenErr,AcceptErr
};#define backlog 10class Sock
{
public:Sock(){}~Sock(){}
public:void Socket(){sockfd_ = socket(AF_INET,SOCK_STREAM,0);if(sockfd_ < 0){lg(Fatal,"socker error, %s:%d",strerror(errno),errno);exit(SocketErr);}   }void Bind(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;if(bind(sockfd_,(struct sockaddr *)&local,sizeof(local)) < 0){lg(Fatal,"bind error, %s: %d", strerror(errno), errno);exit(BindErr);}}void Listen(){if(listen(sockfd_ , backlog) < 0){lg(Fatal,"listen error, %s: %d", strerror(errno), errno);exit(ListenErr);}}int Accept(std::string* clientip, std::uint16_t* clientport){struct sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(sockfd_,(struct sockaddr*)&peer,&len);if(newfd < 0){lg(Warning,"accept error, %s: %d", strerror(errno), errno);exit(AcceptErr);}char ipstr[64];inet_ntop(AF_INET,&(peer.sin_addr.s_addr),ipstr,sizeof(ipstr));*clientip = ipstr;*clientport = ntohs(peer.sin_port);return newfd;}bool Connect(const std::string &ip, const uint16_t &port){struct sockaddr_in peer;memset(&peer,0,sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = htons(port);inet_pton(AF_INET,ip.c_str(),&(peer.sin_addr.s_addr));int n = connect(sockfd_,(struct sockaddr*)&peer,sizeof(peer));if(n == -1){std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;return false;}return true;}void Close(){close(sockfd_);}int FD(){return sockfd_;}private:int sockfd_;
};

服務(wù)端代碼

首先,我們需要初始化服務(wù)器,這包括三個(gè)關(guān)鍵步驟:

  1. 使用socket函數(shù)來(lái)創(chuàng)建一個(gè)新的套接字。
  2. 接著,通過(guò)bind函數(shù),我們將這個(gè)套接字綁定到一個(gè)特定的端口號(hào)上,這樣客戶端就可以通過(guò)這個(gè)端口與服務(wù)器建立連接。
  3. 然后,通過(guò)調(diào)用listen函數(shù),我們將套接字設(shè)置為監(jiān)聽狀態(tài),等待客戶端的連接請(qǐng)求。

服務(wù)器初始化完成后,就可以啟動(dòng)它了。啟動(dòng)后,服務(wù)器的主要任務(wù)是不斷地調(diào)用accept函數(shù),從監(jiān)聽套接字中接收新的連接請(qǐng)求。每當(dāng)成功接受到一個(gè)新連接時(shí),服務(wù)器會(huì)創(chuàng)建一個(gè)新的進(jìn)程。這個(gè)新進(jìn)程將負(fù)責(zé)為該客戶端提供計(jì)算服務(wù),確保每個(gè)客戶端都能得到及時(shí)且獨(dú)立的響應(yīng)。?

TcpServer.hpp?

#pragma once#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <functional>
#include "Socket.hpp"//這允許我們?yōu)?TCP 服務(wù)器提供一個(gè)自定義的回調(diào)函數(shù),該函數(shù)處理從客戶端接收到的數(shù)據(jù)。
using func_t = std::function<std::string(std::string &package)>; //std::function對(duì)象,該對(duì)象接受一個(gè) std::string 引用作為參數(shù)并返回一個(gè) std::stringclass TcpServer
{
public:TcpServer(uint16_t port,func_t callback):port_(port),callback_(callback){}//初始化tcp服務(wù)器bool InitServer(){listensock_.Socket();listensock_.Bind(port_);listensock_.Listen();lg(Info,"init server .... done");return true;}//啟動(dòng)服務(wù)器void Start(){   signal(SIGCHLD, SIG_IGN);//忽略了 SIGCHLD 和 SIGPIPE 信號(hào),當(dāng)子進(jìn)程終止或管道寫入失敗時(shí),服務(wù)器不會(huì)接收到這些信號(hào)。signal(SIGPIPE, SIG_IGN);//無(wú)限循環(huán),在該循環(huán)中,它嘗試接受新的客戶端連接。對(duì)于每個(gè)新的連接,它創(chuàng)建一個(gè)子進(jìn)程來(lái)處理該連接。//并使用之前提供的回調(diào)函數(shù)來(lái)處理這些數(shù)據(jù)。如果回調(diào)函數(shù)返回一個(gè)非空字符串,那么該字符串將被發(fā)送回客戶端。while (true){std::string clientip;uint16_t clientport;int sockfd = listensock_.Accept(&clientip,&clientport);if(sockfd < 0)continue;// 返回繼續(xù)監(jiān)聽       lg(Info,"accept a new link, sockfd: %d, clientip: %s, clientport: %d",sockfd, clientip, clientport);// 提供服務(wù)if(fork() == 0)//{listensock_.Close();//在子進(jìn)程中,服務(wù)器不再需要監(jiān)聽套接字,調(diào)用 listensock_.Close(); 關(guān)閉監(jiān)聽套接字,釋放相關(guān)的系統(tǒng)資源。  std::string inbuffer_stream;//數(shù)據(jù)計(jì)算while (true){char buffer[1280];ssize_t n = read(sockfd,buffer,sizeof(buffer));if(n > 0)   {buffer[n] = 0;inbuffer_stream += buffer;while(true){std::string info = callback_(inbuffer_stream);if(info.empty())break;write(sockfd, info.c_str(), info.size());}}else if(n == 0)break;elsebreak;}   exit(0);//}close(sockfd);}   }~TcpServer(){}
private:uint16_t port_;Sock listensock_;func_t callback_;
};

說(shuō)明一下:?

  • 當(dāng)前服務(wù)器采用的是多進(jìn)程的方案,對(duì)于每個(gè)新的連接,創(chuàng)建一個(gè)子進(jìn)程來(lái)處理該連接。
  • 提供的回調(diào)函數(shù)來(lái)處理客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù)。如果回調(diào)函數(shù)返回一個(gè)非空字符串,那么該字符串將被發(fā)送回客戶端。

服務(wù)進(jìn)程執(zhí)行例程

當(dāng)服務(wù)端調(diào)用accept函數(shù)獲取到新連接并創(chuàng)建新進(jìn)程后,該線程就需要為該客戶端提供計(jì)算服務(wù),此時(shí)該進(jìn)程需要先讀取客戶端發(fā)來(lái)的計(jì)算請(qǐng)求,然后進(jìn)行對(duì)應(yīng)的計(jì)算操作,如果客戶端發(fā)來(lái)的計(jì)算請(qǐng)求存在除0、模0、非法運(yùn)算等問(wèn)題,就將響應(yīng)結(jié)構(gòu)體當(dāng)中的狀態(tài)字段對(duì)應(yīng)設(shè)置為1、2、3即可。

ServerCal.hpp?

#pragma once
#include <iostream>
#include "Protocol.hpp"enum
{DivZero = 1,ModZero,Other_Oper
};class ServerCal
{
public:ServerCal(){}Response CalculatorHelper(const Request &req){Response resp(0, 0);switch (req.op){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.result = req.x / req.y;elseresp.code = DivZero;}break;case '%':{if (req.y != 0)resp.result = req.x % req.y;elseresp.code = ModZero;}break;default:resp.code = Other_Oper;break;}return resp;}// "len"\n"10 + 20"\nstd::string Calculator(std::string &package){std::string content;bool r = Decode(package, &content); // "len"\n"10 + 20"\nif (!r)return "";// "10 + 20"Request req;r = req.Deserialize(content); // 反序列化完req中的變量就拿到值了if (!r)return "";content = "";                          //清空Response resp = CalculatorHelper(req); // result=30 code=0;// 計(jì)算完進(jìn)行序列化resp.Serialize(&content);content = Encode(content);return content;}~ServerCal(){}
};

啟動(dòng)網(wǎng)絡(luò)版服務(wù)端

ServerCal.cpp

前面我們?cè)赥cpServer.hpp封裝了服務(wù)器初始化和啟動(dòng)服務(wù)器函數(shù)的類,以及ServerCal類實(shí)現(xiàn)網(wǎng)絡(luò)版計(jì)算器的類執(zhí)行例程。下面我們實(shí)現(xiàn)一個(gè)ServerCal.cpp來(lái)啟動(dòng)網(wǎng)絡(luò)版服務(wù)器,只有要調(diào)用前面兩個(gè)類實(shí)現(xiàn)的接口即可。

  • 從命令行參數(shù)獲取端口號(hào)
  • 創(chuàng)建ServerCal實(shí)例
  • 綁定ServerCal的Calculator方法
  • 創(chuàng)建TcpServer實(shí)例,并將綁定的Calculator方法和端口號(hào)作為參數(shù)傳遞給它。
  • 調(diào)用InitServer方法初始化服務(wù)器
  • 最后調(diào)用Start方法啟動(dòng)服務(wù)器。
#include "TcpServer.hpp"
#include "ServerCal.hpp"void Usage(const std::string &proc)
{std::cout << "\nUsage: " << proc << " port\n" << std::endl; 
}int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]);ServerCal cal;//std::bind是C++標(biāo)準(zhǔn)庫(kù)中的一個(gè)函數(shù)模板,它可以將一個(gè)可調(diào)用對(duì)象(如函數(shù)、lambda函數(shù)或成員函數(shù)指針)與其參數(shù)綁定,生成一個(gè)新的可調(diào)用對(duì)象。//&ServerCal::Calculator是ServerCal類中的Calculator成員函數(shù)的指針。//&cal是ServerCal類的一個(gè)實(shí)例的地址,該實(shí)例用于調(diào)用Calculator成員函數(shù)。//std::placeholders::_1是一個(gè)占位符,它表示bind生成的新可調(diào)用對(duì)象接受的第一個(gè)參數(shù)將傳遞給Calculator成員函數(shù)作為它的第一個(gè)參數(shù)。TcpServer *tsvp = new TcpServer(port,std::bind(&ServerCal::Calculator,&cal,std::placeholders::_1));tsvp->InitServer();tsvp->Start();return 0;
}

協(xié)議定制

為了實(shí)現(xiàn)一個(gè)網(wǎng)絡(luò)版的計(jì)算器,確保通信雙方遵循共同的規(guī)則和約定是至關(guān)重要的。這就需要我們制定一套簡(jiǎn)明的協(xié)議。數(shù)據(jù)的交互通常涉及請(qǐng)求數(shù)據(jù)和響應(yīng)數(shù)據(jù),因此需要分別定義兩者的結(jié)構(gòu)。在實(shí)現(xiàn)層面,C++允許通過(guò)類來(lái)組織代碼和數(shù)據(jù),但同樣也可以使用更簡(jiǎn)單的結(jié)構(gòu)體來(lái)定義數(shù)據(jù)結(jié)構(gòu)??紤]到簡(jiǎn)潔性和直接性,這里我們選擇使用結(jié)構(gòu)體來(lái)定義請(qǐng)求和響應(yīng)的數(shù)據(jù)格式。

因此我們需要設(shè)計(jì)一個(gè)請(qǐng)求結(jié)構(gòu)體,用于封裝從客戶端發(fā)送到服務(wù)器的計(jì)算請(qǐng)求信息,以及一個(gè)響應(yīng)結(jié)構(gòu)體,用于封裝服務(wù)器處理完請(qǐng)求后返回給客戶端的結(jié)果。通過(guò)這種方式,我們可以確保通信雙方按照預(yù)定的格式發(fā)送和接收數(shù)據(jù),從而實(shí)現(xiàn)網(wǎng)絡(luò)計(jì)算器的功能。

  • 請(qǐng)求結(jié)構(gòu)體中需要包括兩個(gè)操作數(shù),以及對(duì)應(yīng)需要進(jìn)行的操作。
  • 響應(yīng)結(jié)構(gòu)體中需要包括一個(gè)計(jì)算結(jié)果,除此之外,響應(yīng)結(jié)構(gòu)體中還需要包括一個(gè)狀態(tài)字段,表示本次計(jì)算的狀態(tài),因?yàn)榭蛻舳税l(fā)來(lái)的計(jì)算請(qǐng)求可能是無(wú)意義的。
  • 請(qǐng)求結(jié)構(gòu)體和響應(yīng)結(jié)構(gòu)體當(dāng)中都封裝了序列化函數(shù)和反序列化函數(shù)。
  • 我們?cè)陬愅庠O(shè)置了編碼函數(shù)和解碼函數(shù)
#pragma once#include <iostream>
#include <string>const std::string blank_space_sep = " ";
const std::string protocol_sep  = "\n";std::string Encode(std::string &content)
{std::string package = std::to_string(content.size());package += protocol_sep;package += content;package += protocol_sep;return package;
}// "len"\n"x op y"\nXXXXXX
// "protocolnumber"\n"len"\n"x op y"\nXXXXXX
bool Decode(std::string &package, std::string *content)
{size_t pos = package.find(protocol_sep);if(pos == std::string::npos) return false;std::string len_str = package.substr(0,pos);std::size_t len = std::stoi(len_str);std::size_t total_len = len_str.size() + len + 2;if(package.size() < total_len)  return false;//傳入的序列化字符串沒(méi)有達(dá)到報(bào)頭提供的字符串長(zhǎng)度*content = package.substr(pos+1,len);package.erase(0,total_len);//return true;
}class Request
{
public:Request(int data1,int data2,char oper):x(data1),y(data2),op(oper){}Request()//{}
public:bool Serialize(std::string *out){// 構(gòu)建報(bào)文的有效載荷// struct => string, "x op y"std::string s = std::to_string(x);s += blank_space_sep;s += op;s += blank_space_sep;s += std::to_string(y);*out = s;return true;}bool Deserialize(const std::string &in)// "x op y"{std::size_t left = in.find(blank_space_sep);if(left == std::string::npos)return false;std::string part_x = in.substr(0,left);std::size_t right = in.rfind(blank_space_sep);if(right == std::string::npos)return false;std::string part_y = in.substr(right);if(left+2 != right)return false;op = in[left + 1];x = std::stoi(part_x);x = std::stoi(part_y);return true;}void DebugPrint(){std::cout << "新請(qǐng)求構(gòu)建完成:  " << x << op << y << "=?" << std::endl;}public://x op yint x;int y;char op;//+ - * / %
};class Response
{
public:Response(int res,int c):result(res),code(c){}Response(){}
public:bool Serialize(std::string *out){// "result code"// 構(gòu)建報(bào)文的有效載荷std::string s = std::to_string(result);s += blank_space_sep;s += std::to_string(code);*out = s;return true;}bool Deserialize(const std::string &in){std::size_t pos = in.find(blank_space_sep);if(pos == std::string::npos) return false;std::string part_left = in.substr(0,pos);std::string part_right = in.substr(pos+1);result = std::stoi(part_left);code = std::stoi(part_right);return true;}void DebugPrint(){std::cout << "結(jié)果響應(yīng)完成, result: " << result << ", code: "<< code << std::endl;}public:int result;int code;//0表示結(jié)果是可信的;否則!0具體是幾,表明對(duì)應(yīng)的錯(cuò)誤原因
};

請(qǐng)求結(jié)構(gòu)體

  • 序列化函數(shù)用于構(gòu)建報(bào)文的有效載荷,將 `Request` 對(duì)象轉(zhuǎn)換為一個(gè)字符串。它首先將 `x` 和 `y` 轉(zhuǎn)換為字符串,并使用空格和操作符 `op` 將它們連接在一起。例如,如果 `x` 是 5,`y` 是 3,并且 `op` 是 `+`,則生成的字符串將是 `"5 + 3"`
  • 反序列化函數(shù)嘗試從一個(gè)字符串中恢復(fù)一個(gè) `Request` 對(duì)象。它首先查找空格和操作符來(lái)分隔 `x`、`op` 和 `y`。然后,它將這些部分轉(zhuǎn)換回它們的原始類型,并檢查字符串的格式是否正確。如果一切正常,它將更新 `Request` 對(duì)象的 `x`、`y` 和 `op`。

響應(yīng)結(jié)構(gòu)體

  • Serialize 函數(shù)接受一個(gè)指向 std::string 的指針 out,并將 result 和 code 成員變量的值轉(zhuǎn)換為字符串,然后用空格(blank_space_sep)分隔它們,并將結(jié)果字符串存儲(chǔ)在 out 所指向的位置。函數(shù)總是返回 true,表示序列化操作總是成功的。
  • Deserialize 函數(shù)接受一個(gè)常量字符串引用 in,并嘗試從中解析出 result 和 code 的值。它首先查找空格的位置,然后提取空格前后的兩個(gè)子字符串,并將它們分別轉(zhuǎn)換為整數(shù)來(lái)更新 result 和 code 的值。如果字符串中沒(méi)有找到空格,函數(shù)返回 false,否則返回 true。

編碼函數(shù) Encode:

  • 函數(shù)接受一個(gè)字符串 content,并返回一個(gè)編碼后的字符串 package。
  • 首先,將 `content` 的大小(長(zhǎng)度)轉(zhuǎn)換為字符串并添加到 `package`。 ?
  • 然后,添加一個(gè)換行符。 ?
  • 接著,添加原始的 `content`。 ?
  • 最后,再添加一個(gè)換行符。這樣,編碼后的字符串格式是:`"length\ncontent\n"`。

解碼函數(shù) Decode:

  • 這個(gè)函數(shù)嘗試從給定的 package 字符串中解碼出 content。它首先查找換行符來(lái)確定 content 的長(zhǎng)度,并檢查 package 是否包含足夠的數(shù)據(jù)。如果成功,它會(huì)提取 content 并從 package 中刪除已解碼的部分。

注意:

  • 編碼函數(shù)和解碼函數(shù)是多個(gè)結(jié)構(gòu)體或類都可能需要的共同操作,因此將它們放在類外作為獨(dú)立的函數(shù)。這種做法不僅增強(qiáng)了代碼的可重用性,還方便了協(xié)議的編碼和解碼邏輯的更換。通過(guò)將編碼和解碼邏輯與具體的數(shù)據(jù)結(jié)構(gòu)分離,我們可以在不修改數(shù)據(jù)結(jié)構(gòu)定義的情況下更換編碼和解碼的實(shí)現(xiàn),從而實(shí)現(xiàn)了更好的模塊化和可擴(kuò)展性。

規(guī)定狀態(tài)字段對(duì)應(yīng)的含義:

  • 狀態(tài)字段為0,表示計(jì)算成功。
  • 狀態(tài)字段為1,表示出現(xiàn)除0錯(cuò)誤。
  • 狀態(tài)字段為2,表示出現(xiàn)模0錯(cuò)誤。
  • 狀態(tài)字段為3,表示非法計(jì)算。

此時(shí)我們就完成了協(xié)議的設(shè)計(jì),但需要注意,只有當(dāng)響應(yīng)結(jié)構(gòu)體當(dāng)中的狀態(tài)字段為0時(shí),計(jì)算結(jié)果才是有意義的,否則計(jì)算結(jié)果無(wú)意義。?

客戶端代碼

客戶端首先也需要進(jìn)行初始化:

  • 調(diào)用socket函數(shù),創(chuàng)建套接字。

客戶端初始化完畢后需要調(diào)用connect函數(shù)連接服務(wù)端,當(dāng)連接服務(wù)端成功后,客戶端就可以向服務(wù)端發(fā)起計(jì)算請(qǐng)求了。這里可以讓用戶輸入兩個(gè)操作數(shù)和一個(gè)操作符構(gòu)建一個(gè)計(jì)算請(qǐng)求,然后將該請(qǐng)求發(fā)送給服務(wù)端。而當(dāng)服務(wù)端處理完該計(jì)算請(qǐng)求后,會(huì)對(duì)客戶端進(jìn)行響應(yīng),因此客戶端發(fā)送完請(qǐng)求后還需要讀取服務(wù)端發(fā)來(lái)的響應(yīng)數(shù)據(jù)。

客戶端在向服務(wù)端發(fā)送或接收數(shù)據(jù)時(shí),可以使用write或read函數(shù)進(jìn)行發(fā)送或接收(也可以使用send或recv函數(shù)對(duì)應(yīng)進(jìn)行發(fā)送或接收。)

  • 連接服務(wù)器

由于我們前面封裝了TCP套接字(socket)類的實(shí)現(xiàn),這里我們之間調(diào)用我們封裝的接口即可,下面是客戶端代碼:

客戶端代碼:

#include <iostream>
#include <string>
#include <time.h>
#include <assert.h>
#include "Protocol.hpp"
#include"Socket.hpp"void Usage(const std::string &proc)
{std::cout << "\nUsage: " << proc << " serverip serverport\n" << std::endl;
}// ./clientcal ip port
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);//創(chuàng)建套接字Sock sockfd;sockfd.Socket();//鏈接服務(wù)器bool r = sockfd.Connect(serverip,serverport);if(!r) return 1;srand(time(nullptr)^getpid());int cnt = 1;const std::string opers = "+-*/%=-=&^";std::string inbuffer_stream;while(cnt <= 10){//準(zhǔn)備數(shù)據(jù)std::cout << "===============第" << cnt << "次測(cè)試....., " << "===============" << std::endl;int x = rand() % 100 + 1;usleep(1000);int y = rand() % 100 + 1;usleep(1000);char op = opers[rand() % opers.size()];Request req(x,y,op);req.DebugPrint();//客戶端發(fā)送請(qǐng)求std::string packge;req.Serialize(&packge);packge = Encode(packge);write(sockfd.FD(),packge.c_str(),packge.size());//接受請(qǐng)求響應(yīng)char buffer[128];ssize_t n = read(sockfd.FD(),buffer,sizeof(buffer));// 我們也無(wú)法保證我們能讀到一個(gè)完整的報(bào)文if(n > 0){buffer[n] = 0;inbuffer_stream += buffer;// "len"\n"result code"\nstd::cout << inbuffer_stream << std::endl;std::string content;bool r = Decode(inbuffer_stream,&content);// "result code"assert(r);Response resp;r = resp.Deserialize(content);assert(r);resp.DebugPrint();}std::cout << "=================================================" << std::endl;sleep(1);cnt++;}sockfd.Close();return 0;
}

代碼測(cè)試

運(yùn)行服務(wù)端后再讓客戶端連接服務(wù)端,此時(shí)服務(wù)端就會(huì)對(duì)客戶端發(fā)來(lái)的計(jì)算請(qǐng)求進(jìn)行處理,并會(huì)將計(jì)算后的結(jié)果響應(yīng)給客戶端。

我們看到如果客戶端要進(jìn)行除0、模0、非法運(yùn)算,在服務(wù)端識(shí)別后就會(huì)按照約定對(duì)應(yīng)將響應(yīng)數(shù)據(jù)的狀態(tài)碼設(shè)置為1、2、3,此時(shí)響應(yīng)狀態(tài)碼為非零,因此在客戶端打印出來(lái)的計(jì)算結(jié)果就是沒(méi)有意義的。

此時(shí)我們就以這樣一種方式約定出了一套應(yīng)用層的簡(jiǎn)單的網(wǎng)絡(luò)計(jì)算器,這就叫做協(xié)議。?

使用JSON進(jìn)行序列化與反序列化

上面我們進(jìn)行序列化和反序列化是自己進(jìn)行協(xié)議定制,其實(shí)我們也可以用JSON或者Protobuf進(jìn)行數(shù)據(jù)的序列化和反序列化操作。

JSON (JavaScript Object Notation) 和 Protobuf (Protocol Buffers) 都是數(shù)據(jù)序列化格式,但它們?cè)谠O(shè)計(jì)目標(biāo)、性能、使用場(chǎng)景等方面有所不同。

下面我們主要來(lái)介紹一下使用JSON進(jìn)行序列化和反序列化操作:

JSON

  • 設(shè)計(jì)目標(biāo):JSON 主要用于人類可讀性和易于編寫。它是基于 JavaScript 的子集,但不僅限于 JavaScript 使用。
  • 性能:JSON 的解析和序列化速度相對(duì)較慢,尤其是對(duì)于大型數(shù)據(jù)結(jié)構(gòu)。
  • 使用場(chǎng)景:JSON 廣泛用于 API 通信、配置文件、Web 存儲(chǔ)等場(chǎng)景,因?yàn)樗子陂喿x和編寫,并且跨語(yǔ)言、跨平臺(tái)。
  • 在使用 JsonCpp 之前,你需要確保已經(jīng)安裝了這個(gè)庫(kù)。
sudo yum install -y jsoncpp-devel
  • 安裝完成后,項(xiàng)目中加入頭文件#include <jsoncpp/json/json.h>
  • 編譯命令后面加上-ljsoncpp

下面是一個(gè)簡(jiǎn)單的示例,展示了如何使用 JsonCpp 來(lái)解析和生成 JSON 數(shù)據(jù):

#include <iostream>  
#include <jsoncpp/json/json.h>  
#include <unistd.h>int main() {  // 創(chuàng)建一個(gè) JSON 對(duì)象  Json::Value root; // 將用于存儲(chǔ) JSON 數(shù)據(jù)的根對(duì)象  root["x"] = 40;  root["y"] = 30;  root["op"] = '+';  root["desc"] = "this is a + oper";  // 序列化:將 JSON 對(duì)象轉(zhuǎn)換為字符串  Json::FastWriter writer;  //Json::StyledWriter writer;  //StyledWriter比Fastwriter多加了\n,可讀性比較好std::string jsonString = writer.write(root);  // 輸出 JSON 字符串  std::cout << "JSON string: " << jsonString << std::endl;  sleep(3);// 反序列化:從字符串解析 JSON  Json::Value v;  Json::Reader Reader;  Reader.parse(jsonString,v);// 訪問(wèn) JSON 對(duì)象中的值  int x = v["x"].asInt();  int y = v["y"].asInt();  char op = v["op"].asInt();  std::string desc = v["desc"].asString();  // 輸出解析后的值  std::cout << x <<std::endl;  std::cout << y <<std::endl;      std::cout << op <<std::endl;      std::cout << desc <<std::endl;     return 0;  
}

運(yùn)行結(jié)果:

有了上面對(duì)JSON基本使用的理解后,下面我們?cè)诰W(wǎng)絡(luò)版計(jì)算器的協(xié)議定制的代碼中增加JSON方式的序列化與反序列化:

我們根據(jù)是否定義了MySelf宏,來(lái)選擇使用兩種序列化方式:

#pragma once#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>// #define MySelf 1const std::string blank_space_sep = " ";
const std::string protocol_sep  = "\n";std::string Encode(std::string &content)
{std::string package = std::to_string(content.size());package += protocol_sep;package += content;package += protocol_sep;return package;
}// "len"\n"x op y"\nXXXXXX
// "protocolnumber"\n"len"\n"x op y"\nXXXXXX
bool Decode(std::string &package, std::string *content)
{size_t pos = package.find(protocol_sep);if(pos == std::string::npos) return false;std::string len_str = package.substr(0,pos);std::size_t len = std::stoi(len_str);std::size_t total_len = len_str.size() + len + 2;if(package.size() < total_len)  return false;//傳入的序列化字符串沒(méi)有達(dá)到報(bào)頭提供的字符串長(zhǎng)度*content = package.substr(pos+1,len);package.erase(0,total_len);//return true;
}// json, protobuf
class Request
{
public:Request(int data1,int data2,char oper):x(data1),y(data2),op(oper){}Request()//{}
public:bool Serialize(std::string *out){
#ifdef MySelf// 構(gòu)建報(bào)文的有效載荷// struct => string, "x op y"std::string s = std::to_string(x);s += blank_space_sep;s += op;s += blank_space_sep;s += std::to_string(y);*out = s;return true;
#elseJson::Value root;root["x"] = x;root["y"] = y;root["op"] = op;// Json::FastWriter w;Json::StyledWriter w;*out = w.write(root);return true;
#endif}bool Deserialize(const std::string &in)// "x op y"{
#ifdef MySelfstd::size_t left = in.find(blank_space_sep);if(left == std::string::npos)return false;std::string part_x = in.substr(0,left);std::size_t right = in.rfind(blank_space_sep);if(right == std::string::npos)return false;std::string part_y = in.substr(right);if(left+2 != right)return false;op = in[left + 1];x = std::stoi(part_x);y = std::stoi(part_y);return true;
#elseJson::Value root;Json::Reader r;r.parse(in,root);x = root["x"].asInt();y = root["y"].asInt();char op = root["op"].asInt();return true;
#endif}void DebugPrint(){std::cout << "新請(qǐng)求構(gòu)建完成:  " << x << op << y << "=?" << std::endl;}public://x op yint x;int y;char op;//+ - * / %
};class Response
{
public:Response(int res,int c):result(res),code(c){}Response(){}
public:bool Serialize(std::string *out){
#ifdef MySelf// "result code"// 構(gòu)建報(bào)文的有效載荷std::string s = std::to_string(result);s += blank_space_sep;s += std::to_string(code);*out = s;return true;
#elseJson::Value root;root["result"] = result;root["code"] = code;// Json::FastWriter w;Json::StyledWriter w;*out = w.write(root);return true;
#endif}bool Deserialize(const std::string &in){
#ifdef MySelfstd::size_t pos = in.find(blank_space_sep);if(pos == std::string::npos) return false;std::string part_left = in.substr(0,pos);std::string part_right = in.substr(pos+1);result = std::stoi(part_left);code = std::stoi(part_right);return true;
#elseJson::Value root;Json::Reader r;r.parse(in,root);int result = root["result"].asInt();int code = root["code"].asInt();
#endif}void DebugPrint(){std::cout << "結(jié)果響應(yīng)完成, result: " << result << ", code: "<< code << std::endl;}public:int result;int code;//0表示結(jié)果是可信的;否則!0具體是幾,表明對(duì)應(yīng)的錯(cuò)誤原因
};

makefile文件:

編譯時(shí)要加上-ljsoncpp選項(xiàng),我們也可以在makefile文件中進(jìn)行宏定義Myself

.PHONY:all
all:servercal clientcal# Flag= -DMySelf=1
Flag= #-DMySelf=1
Lib=-ljsoncppservercal:ServerCal.cppg++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.cppg++ -o $@ $^ -std=c++11 $(Lib) $(Flag).PHONY:clean
clean:rm -f servercal clientcal

代碼測(cè)試:

以上我們就成功用Json實(shí)現(xiàn)了數(shù)據(jù)序列化和反序列化。

http://www.risenshineclean.com/news/6699.html

相關(guān)文章:

  • 深圳市網(wǎng)站建設(shè)公網(wǎng)絡(luò)搭建是干什么的
  • 設(shè)計(jì)電子商務(wù)網(wǎng)站主頁(yè)鄭州seo教程
  • 江西建筑培訓(xùn)網(wǎng)seo就業(yè)哪家好
  • 做公司網(wǎng)站需要注意什么護(hù)膚品營(yíng)銷策劃方案
  • 印刷網(wǎng)站建設(shè)建立網(wǎng)站怎么搞
  • 杭州品牌網(wǎng)站制作培訓(xùn)班
  • 寵物店做網(wǎng)站的論文深圳網(wǎng)絡(luò)推廣
  • 專業(yè)網(wǎng)站推廣引流外鏈圖片
  • 英文網(wǎng)站模板改成中文成都百度快照優(yōu)化排名
  • 免費(fèi)域名申請(qǐng)哪個(gè)網(wǎng)站好百度營(yíng)銷客戶端
  • 自己創(chuàng)業(yè)做原公司一樣的網(wǎng)站武漢網(wǎng)站排名提升
  • 找設(shè)計(jì)公司上哪個(gè)網(wǎng)站網(wǎng)絡(luò)營(yíng)銷課程主要講什么內(nèi)容
  • 做電商要有網(wǎng)站嗎seo關(guān)鍵詞排名優(yōu)化報(bào)價(jià)
  • 做網(wǎng)站找哪家怎么創(chuàng)建網(wǎng)站?
  • 動(dòng)態(tài)網(wǎng)站做優(yōu)化搭建網(wǎng)站需要什么技術(shù)
  • 建設(shè)銀行上海分行網(wǎng)站網(wǎng)站seo快速排名優(yōu)化的軟件
  • 儀征網(wǎng)站建設(shè)友鏈查詢站長(zhǎng)工具
  • 手機(jī)開網(wǎng)店用什么軟件seo優(yōu)化排名
  • 婚紗攝影的網(wǎng)站怎么做推廣網(wǎng)站哪個(gè)好
  • 做網(wǎng)站怎樣辦營(yíng)業(yè)執(zhí)照搜狗網(wǎng)頁(yè)版入口
  • 用vs做網(wǎng)站如何連接數(shù)據(jù)庫(kù)最新國(guó)內(nèi)你新聞
  • 手機(jī)版網(wǎng)站建設(shè)百度競(jìng)價(jià)包年推廣是怎么回事
  • 財(cái)政部網(wǎng)站官網(wǎng) PPP項(xiàng)目建設(shè)關(guān)鍵詞排名查詢網(wǎng)站
  • 網(wǎng)站建設(shè)對(duì)產(chǎn)品推銷作用大嗎百度云搜索引擎入口盤多多
  • 黑龍江建設(shè)廳網(wǎng)站官網(wǎng)做網(wǎng)站優(yōu)化哪家公司好
  • 給網(wǎng)站整一個(gè)客服 怎么做鳳凰網(wǎng)全國(guó)疫情實(shí)時(shí)動(dòng)態(tài)
  • 上海網(wǎng)站建設(shè)設(shè)計(jì)公司排名無(wú)錫百度推廣開戶
  • 上海網(wǎng)站建設(shè)免費(fèi)推薦上海網(wǎng)站排名優(yōu)化怎么做
  • 常用wap網(wǎng)站開發(fā)工具 手機(jī)網(wǎng)站制作軟件競(jìng)價(jià)托管咨詢微競(jìng)價(jià)
  • 企業(yè)做網(wǎng)站的流程某個(gè)網(wǎng)站seo分析實(shí)例