為什么要序列化?
如果光看定義很難理解序列化的意義,那么我們可以從另一個角度來推導出什么是序列化, 那么究竟序列化的目的是什么?
其實序列化最終的目的是為了對象可以跨平臺存儲,和進行網(wǎng)絡傳輸。而我們進行跨平臺存儲和網(wǎng)絡傳輸?shù)姆绞骄褪荌O,而我們的IO支持的數(shù)據(jù)格式就是字節(jié)數(shù)組。
因為我們單方面的只把對象轉成字節(jié)數(shù)組還不行,因為沒有規(guī)則的字節(jié)數(shù)組我們是沒辦法把對象的本來面目還原回來的,所以我們必須在把對象轉成字節(jié)數(shù)組的時候就制定一種規(guī)則(序列化),那么我們從IO流里面讀出數(shù)據(jù)的時候再以這種規(guī)則把對象還原回來(反序列化)。
如果我們要把一棟房子從一個地方運輸?shù)搅硪粋€地方去,序列化就是我把房子拆成一個個的磚塊放到車子里,然后留下一張房子原來結構的圖紙,反序列化就是我們把房子運輸?shù)搅四康牡匾院?,根?jù)圖紙把一塊塊磚頭還原成房子原來面目的過程。
是需要進行“跨平臺存儲”和”網(wǎng)絡傳輸”的數(shù)據(jù),都需要進行序列化。本質上存儲和網(wǎng)絡傳輸 都需要經(jīng)過 把一個對象狀態(tài)保存成一種跨平臺識別的字節(jié)格式,然后其他的平臺才可以通過字節(jié)信息解析還原對象信息。
序列化只是一種拆裝組裝對象的規(guī)則,那么這種規(guī)則肯定也可能有多種多樣,比如現(xiàn)在常見的序列化方式有:
JDK(不支持跨語言)、JSON、XML、Hessian、Kryo(不支持跨語言)、Thrift、Protostuff、FST(不支持跨語言)
在進行網(wǎng)絡傳輸之前,我們要將數(shù)據(jù)轉換成字符串的形式,以字節(jié)流的方式發(fā)送信息。雙方在通信時如果發(fā)送端一直發(fā)送數(shù)據(jù),但是接收端并沒有及時的接收數(shù)據(jù),那這一對的數(shù)據(jù)就會被堆積下來。等接收端能開始接收數(shù)據(jù)時該如何保證報文和報文的邊界的呢?保證讀到的時一個報文而不是半個或者1.5個報文的呢?
常見的解決方法:
1. 定? 長:? ? ?即可以規(guī)定一個報文的大小為1024字節(jié),讀取時嚴格的按照定下的長度讀取
2.特殊符號:在報文的結束端加上特殊符號“ + ” " -?"等特殊符號以此區(qū)分不同的報文
3.自描述方式:規(guī)定報文的頭四個字節(jié)存儲的是報文的有效長度,未來在讀取報文的時候首先讀取這頭四個字節(jié),根據(jù)這四個字節(jié)存儲的長度然后截取這個報文。
協(xié)議的實現(xiàn)
接下來將采用序列化和反序列化+自描述的方式實現(xiàn)一款簡易版網(wǎng)絡計算器。網(wǎng)絡計算器在發(fā)起請求的過程中的格式為:a運算符b ,我們首先創(chuàng)建一個對象將兩個運算的數(shù)和一個運算符存儲起來,因為tcp是面向字節(jié)流的所以在網(wǎng)絡通信前,首先需要將這個對象轉換為字符串的形式,以字節(jié)流的方式發(fā)送數(shù)據(jù)到服務端。上文所講為了方便服務端讀取報文,所以這里采用的是自描述的方式發(fā)送數(shù)據(jù),即在報文的有效載荷前加上一個存儲報文有效長度的報頭,報頭再與報文之間用特殊的字符隔開,未來再服務端進行讀取時就找這個特殊的分隔符,根據(jù)分隔符確定報頭的位置,然后根據(jù)報頭中存儲的有效的長度就可以完整的讀取到一條客戶端的請求。
?客戶端發(fā)起請求就是serialize+enLength(序列化+自描述),服務端在接收到這個報文后首先去掉報頭得到正文,將正文反序列化得到一個包含正文元素的對象(請求對象),此時再創(chuàng)建一個要返回結果的對象(響應對象),將這請求對象和相應對象傳入到計算函數(shù)中,計算函數(shù)將計算的結果和計算的退出碼保存到相應對象中,再將相應對象序列化+添加報頭組成一個大的“字符串”通過網(wǎng)絡發(fā)送到客戶端,客戶端接收后先去報頭再反序列化構建出一個對象,這個對象中就保存著計算的結果和計算的退出碼。
客戶端從網(wǎng)絡接收到數(shù)據(jù)后先去報頭得到正文,反序列化得到對象,對象中所存儲的值就有這次運算的退出碼和運算結果。
?協(xié)議的定制中包括:1分隔符的種類2.添加報頭3.去報頭3.數(shù)據(jù)從緩沖區(qū)的接收4.請求對象的序列化和反序列化 5.相應對象的序列化和反序列化6.運行結果的退出碼
代碼如下:?
#pragma once
#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <jsoncpp/json/json.h>
#define SEP " "
#define SEP_LEN strlen(SEP) // 不敢使用sizeof()
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP) // 不敢使用sizeof()
enum
{
OK = 0,
DIV_ZERO,
MOD_ZERO,
OP_ERROR
};
// "x op y" -> "content_len"\r\n"x op y"\r\n
// "exitcode result" -> "content_len"\r\n"exitcode result"\r\n
std::string enLength(const std::string &text)
{
std::string send_string = std::to_string(text.size());
send_string += LINE_SEP;
send_string += text;
send_string += LINE_SEP;
return send_string;
}
// "content_len"\r\n"exitcode result"\r\n
bool deLength(const std::string &package, std::string *text)
{
auto pos = package.find(LINE_SEP);
if (pos == std::string::npos)
return false;
std::string text_len_string = package.substr(0, pos);
int text_len = std::stoi(text_len_string);
*text = package.substr(pos + LINE_SEP_LEN, text_len);
return true;
}
// 沒有人規(guī)定我們網(wǎng)絡通信的時候,只能有一種協(xié)議??!
// 我們怎么讓系統(tǒng)知道我們用的是哪一種協(xié)議呢??
// "content_len"\r\n"協(xié)議編號"\r\n"x op y"\r\n
class Request
{
public:
Request() : x(0), y(0), op(0)
{
}
Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_)
{
}
// 1. 自己寫
// 2. 用現(xiàn)成的
bool serialize(std::string *out)
{
#ifdef MYSELF
*out = "";
// 結構化 -> "x op y";
std::string x_string = std::to_string(x);
std::string y_string = std::to_string(y);
*out = x_string;
*out += SEP;
*out += op;
*out += SEP;
*out += y_string;
#else
Json::Value root;
root["first"] = x;
root["second"] = y;
root["oper"] = op;
Json::FastWriter writer;
// Json::StyledWriter writer;
*out = writer.write(root);
#endif
return true;
}
// "x op yyyy";
bool deserialize(const std::string &in)
{
#ifdef MYSELF
// "x op y" -> 結構化
auto left = in.find(SEP);
auto right = in.rfind(SEP);
if (left == std::string::npos || right == std::string::npos)
return false;
if (left == right)
return false;
if (right - (left + SEP_LEN) != 1)
return false;
std::string x_string = in.substr(0, left); // [0, 2) [start, end) , start, end - start
std::string y_string = in.substr(right + SEP_LEN);
if (x_string.empty())
return false;
if (y_string.empty())
return false;
x = std::stoi(x_string);
y = std::stoi(y_string);
op = in[left + SEP_LEN];
#else
Json::Value root;
Json::Reader reader;
reader.parse(in, root);
x = root["first"].asInt();
y = root["second"].asInt();
op = root["oper"].asInt();
#endif
return true;
}
public:
// "x op y"
int x;
int y;
char op;
};
class Response
{
public:
Response() : exitcode(0), result(0)
{
}
Response(int exitcode_, int result_) : exitcode(exitcode_), result(result_)
{
}
bool serialize(std::string *out)
{
#ifdef MYSELF
*out = "";
std::string ec_string = std::to_string(exitcode);
std::string res_string = std::to_string(result);
*out = ec_string;
*out += SEP;
*out += res_string;
#else
Json::Value root;
root["exitcode"] = exitcode;
root["result"] = result;
Json::FastWriter writer;
*out = writer.write(root);
#endif
return true;
}
bool deserialize(const std::string &in)
{
#ifdef MYSELF
// "exitcode result"
auto mid = in.find(SEP);
if (mid == std::string::npos)
return false;
std::string ec_string = in.substr(0, mid);
std::string res_string = in.substr(mid + SEP_LEN);
if (ec_string.empty() || res_string.empty())
return false;
exitcode = std::stoi(ec_string);
result = std::stoi(res_string);
#else
Json::Value root;
Json::Reader reader;
reader.parse(in, root);
exitcode = root["exitcode"].asInt();
result = root["result"].asInt();
#endif
return true;
}
public:
int exitcode; // 0:計算成功,!0表示計算失敗,具體是多少,定好標準
int result; // 計算結果
};
// "content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op
bool recvPackage(int sock, std::string &inbuffer, std::string *text)
{
char buffer[1024];
while (true)
{
ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
inbuffer += buffer;
// 分析處理
auto pos = inbuffer.find(LINE_SEP);
if (pos == std::string::npos)
continue;
std::string text_len_string = inbuffer.substr(0, pos);
int text_len = std::stoi(text_len_string);
int total_len = text_len_string.size() + 2 * LINE_SEP_LEN + text_len;
// text_len_string + "\r\n" + text + "\r\n" <= inbuffer.size();
std::cout << "處理前#inbuffer: \n" << inbuffer << std::endl;
if (inbuffer.size() < total_len)
{
std::cout << "你輸入的消息,沒有嚴格遵守我們的協(xié)議,正在等待后續(xù)的內容, continue" << std::endl;
continue;
}
// 至少有一個完整的報文
*text = inbuffer.substr(0, total_len);
inbuffer.erase(0, total_len);
std::cout << "處理后#inbuffer:\n " << inbuffer << std::endl;
break;
}
else
return false;
}
return true;
}
服務端將字符串通過去報頭和反序列化的操作就將客戶端的請求構建出來一個請求對象,服務端再創(chuàng)建一個響應對象,這兩個對象在計算函數(shù)的內部將計算的結果和計算的退出碼一起存儲到響應對象中,至此完成了計算任務,計算函數(shù)如下:
bool cal(const Request &req, Response &resp)
{
resp.exitcode = OK;
resp.result = OK;
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.exitcode = DIV_ZERO;
else
resp.result = req.x / req.y;
}
break;
case '%':
{
if (req.y == 0)
resp.exitcode = MOD_ZERO;
else
resp.result = req.x % req.y;
}
break;
default:
resp.exitcode = OP_ERROR;
break;
}
return true;
}
客戶端啟動后,就會彈出讓用戶輸入計算的式子,這個式子是從鍵盤接收的,首先將接收的式子存到一個零時緩沖區(qū)內,方便我們對輸入的式子切割并完成對請求對象的構建,調用切割的函數(shù)如下:
Request ParseLine(const std::string &line)
{
int status = 0;
int i = 0;
int cnt = line.size();
std::string left, right;
char op;
while (i < cnt)
{
switch (status)
{
case 0:
{
if(!isdigit(line[i]))
{
op = line[i];
status = 1;
}
else left.push_back(line[i++]);
}
break;
case 1:
i++;
status = 2;
break;
case 2:
right.push_back(line[i++]);
break;
}
}
std::cout << std::stoi(left)<<" " << std::stoi(right) << " " << op << std::endl;
return Request(std::stoi(left), std::stoi(right), op);
}
這是一個名為ParseLine的C++函數(shù),它接受一個名為line的std::string參數(shù),并返回一個Request對象。ParseLine函數(shù)首先初始化一些變量,包括status、i、cnt、left、right和op。status跟蹤正在讀取字符串的哪個部分,i跟蹤正在讀取的字符的索引,cnt是輸入字符串的大小,left和right分別保存左操作數(shù)和右操作數(shù),op保存運算符。接下來,ParseLine使用while循環(huán)遍歷輸入字符串中的每個字符。在while循環(huán)內,有一個switch語句,根據(jù)當前狀態(tài)處理不同情況。在第0種情況下,函數(shù)使用isdigit()檢查索引i處的當前字符是否為數(shù)字。如果它不是數(shù)字,則將op設置為當前字符,將狀態(tài)更新為1,并繼續(xù)到下一個字符。否則,它將當前字符附加到left字符串中并增加i。在第1種情況下,它增加i并將狀態(tài)更改為2。在第2種情況下,它將當前字符附加到right字符串中并增加i。while循環(huán)結束后,函數(shù)使用std::cout打印left、right和op的值,并使用解析后的left、right和op值構造一個Request對象并返回它。這樣就完成了請求對象的構建。
對于序列化和反序列化這個操作的過程可以自己完成,也可以使用Json協(xié)議,使用別人定制好的成熟的方法,關于Json第三方庫的安裝,可以使用命令下載Json:
sudo yum install -y jsoncpp-deve
至于要使用自己的方法還是Json協(xié)議,這里可以使用條件編譯的方式,將兩者方法都寫進代碼中
代碼在上方的協(xié)議文件中有體現(xiàn)。
數(shù)據(jù)前面的字符串就是為反序列化建立的索引,先說序列化:Value是Json中的一個萬能對象,用它可以序列化不同格式的數(shù)據(jù),創(chuàng)建Value對象root,root[“x”, _x]就是在root中插入了一個鍵值對,把所有的數(shù)插入到root后,創(chuàng)建FastWrite對象,以root為參數(shù)調用其write方法,用string類型對象str接收write的返回值,str中保存的就是{“op”:43,“x”:1,“y”:1}這樣的字符串,是將數(shù)據(jù)序列化后的結果。
至于Json的反序列化:我們創(chuàng)建Reader類型對象rd與Value類型對象root,調用rd的parse方法,將root和序列化后的字符串str作為parse的參數(shù),parse方法會將反序列化后的結果寫入root,此時我們就能根據(jù)當時創(chuàng)建root對象時,為數(shù)據(jù)建立的索引來還原數(shù)據(jù),比如root[“x”].asInt(),將root中以"x"為key的value值以int的格式返回,這樣我們就能將數(shù)據(jù)還原到我們的結構體中
關于條件編譯,我們可以在makefile文件中,以命令行的方式,在編譯源文件時創(chuàng)建宏,具體是-D 宏的名字
實驗結果演示
?全部代碼:
calClient.cc:
#include "calClient.hpp"
#include <memory>
using namespace std;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " serverip serverport\n\n";
}
// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
unique_ptr<CalClient> tcli(new CalClient(serverip, serverport));
tcli->initClient();
tcli->start();
return 0;
}
calClient.hpp:
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "protocol.hpp"
#define NUM 1024
class CalClient
{
public:
CalClient(const std::string &serverip, const uint16_t &serverport)
: _sock(-1), _serverip(serverip), _serverport(serverport)
{
}
void initClient()
{
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_sock < 0)
{
std::cerr << "socket create error" << std::endl;
exit(2);
}
}
void start()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(_serverport);
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
{
std::cerr << "socket connect error" << std::endl;
}
else
{
std::string line;
std::string inbuffer;
while (true)
{
std::cout << "mycal>>> ";
std::getline(std::cin, line);
Request req = ParseLine(line);
std::string content;
req.serialize(&content);
std::string send_string = enLength(content);
send(_sock, send_string.c_str(), send_string.size(), 0);
std::string package, text;
if (!recvPackage(_sock, inbuffer, &package))
continue;
if (!deLength(package, &text))
continue;
Response resp;
resp.deserialize(text);
std::cout << "exitCode: " << resp.exitcode << std::endl;
std::cout << "result: " << resp.result << std::endl;
}
}
}
Request ParseLine(const std::string &line)
{
int status = 0;
int i = 0;
int cnt = line.size();
std::string left, right;
char op;
while (i < cnt)
{
switch (status)
{
case 0:
{
if(!isdigit(line[i]))
{
op = line[i];
status = 1;
}
else left.push_back(line[i++]);
}
break;
case 1:
i++;
status = 2;
break;
case 2:
right.push_back(line[i++]);
break;
}
}
std::cout << std::stoi(left)<<" " << std::stoi(right) << " " << op << std::endl;
return Request(std::stoi(left), std::stoi(right), op);
}
~CalClient()
{
if (_sock >= 0)
close(_sock);
}
private:
int _sock;
std::string _serverip;
uint16_t _serverport;
};
calServer.cc:
#include "calServer.hpp"
#include <memory>
using namespace server;
using namespace std;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}
bool cal(const Request &req, Response &resp)
{
resp.exitcode = OK;
resp.result = OK;
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.exitcode = DIV_ZERO;
else
resp.result = req.x / req.y;
}
break;
case '%':
{
if (req.y == 0)
resp.exitcode = MOD_ZERO;
else
resp.result = req.x % req.y;
}
break;
default:
resp.exitcode = OP_ERROR;
break;
}
return true;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr<CalServer> tsvr(new CalServer(port));
tsvr->initServer();
tsvr->start(cal);
return 0;
}
calServer.hpp:
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#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 <signal.h>
#include "log.hpp"
#include "protocol.hpp"
namespace server
{
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR
};
static const uint16_t gport = 8080;
static const int gbacklog = 5;
typedef std::function<bool(const Request &req, Response &resp)> func_t;
void handlerEntery(int sock, func_t func)
{
std::string inbuffer;
while (true)
{
std::string req_text, req_str;
if (!recvPackage(sock, inbuffer, &req_text))
return;
std::cout << "帶報頭的請求:\n" << req_text << std::endl;
if (!deLength(req_text, &req_str))
return;
std::cout << "去掉報頭的正文:\n" << req_str << std::endl;
Request req;
if (!req.deserialize(req_str))
return;
Response resp;
func(req, resp);
std::string resp_str;
resp.serialize(&resp_str);
std::cout << "計算完成, 序列化響應: " << resp_str << std::endl;
std::string send_string = enLength(resp_str);
std::cout << "構建完成完整的響應\n" << send_string << std::endl;
send(sock, send_string.c_str(), send_string.size(), 0);
}
}
class CalServer
{
public:
CalServer(const uint16_t &port = gport) : _listensock(-1), _port(port)
{
}
void initServer()
{
_listensock = socket(AF_INET, SOCK_STREAM, 0);
if (_listensock < 0)
{
logMessage(FATAL, "create socket error");
exit(SOCKET_ERR);
}
logMessage(NORMAL, "create socket success: %d", _listensock);
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(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL, "bind socket success");
if (listen(_listensock, gbacklog) < 0)
{
logMessage(FATAL, "listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL, "listen socket success");
}
void start(func_t func)
{
for (;;)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
if (sock < 0)
{
logMessage(ERROR, "accept error, next");
continue;
}
logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?
// version 2 多進程版(2)
pid_t id = fork();
if (id == 0) // child
{
close(_listensock);
// if(fork()>0) exit(0);
// serviceIO(sock);
handlerEntery(sock, func);
close(sock);
exit(0);
}
close(sock);
// father
pid_t ret = waitpid(id, nullptr, 0);
if (ret > 0)
{
logMessage(NORMAL, "wait child success"); // ?
}
}
}
~CalServer() {}
private:
int _listensock;
uint16_t _port;
};
}
日志:
#pragma once
#include <iostream>
#include <string>
#include <cstdarg>
#include <ctime>
#include <unistd.h>
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
const char * to_levelstr(int level)
{
switch(level)
{
case DEBUG : return "DEBUG";
case NORMAL: return "NORMAL";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
case FATAL: return "FATAL";
default : return nullptr;
}
}
void logMessage(int level, const char *format, ...)
{
#define NUM 1024
char logprefix[NUM];
snprintf(logprefix, sizeof(logprefix), "[%s][%ld][pid: %d]",
to_levelstr(level), (long int)time(nullptr), getpid());
char logcontent[NUM];
va_list arg;
va_start(arg, format);
vsnprintf(logcontent, sizeof(logcontent), format, arg);
std::cout << logprefix << logcontent << std::endl;
}
protocol.hpp:文章來源:http://www.zghlxwxcb.cn/news/detail-476253.html
#pragma once
#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <jsoncpp/json/json.h>
#define SEP " "
#define SEP_LEN strlen(SEP) // 不敢使用sizeof()
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP) // 不敢使用sizeof()
enum
{
OK = 0,
DIV_ZERO,
MOD_ZERO,
OP_ERROR
};
// "x op y" -> "content_len"\r\n"x op y"\r\n
// "exitcode result" -> "content_len"\r\n"exitcode result"\r\n
std::string enLength(const std::string &text)
{
std::string send_string = std::to_string(text.size());
send_string += LINE_SEP;
send_string += text;
send_string += LINE_SEP;
return send_string;
}
// "content_len"\r\n"exitcode result"\r\n
bool deLength(const std::string &package, std::string *text)
{
auto pos = package.find(LINE_SEP);
if (pos == std::string::npos)
return false;
std::string text_len_string = package.substr(0, pos);
int text_len = std::stoi(text_len_string);
*text = package.substr(pos + LINE_SEP_LEN, text_len);
return true;
}
// 沒有人規(guī)定我們網(wǎng)絡通信的時候,只能有一種協(xié)議!!
// 我們怎么讓系統(tǒng)知道我們用的是哪一種協(xié)議呢??
// "content_len"\r\n"協(xié)議編號"\r\n"x op y"\r\n
class Request
{
public:
Request() : x(0), y(0), op(0)
{
}
Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_)
{
}
// 1. 自己寫
// 2. 用現(xiàn)成的
bool serialize(std::string *out)
{
#ifdef MYSELF
*out = "";
// 結構化 -> "x op y";
std::string x_string = std::to_string(x);
std::string y_string = std::to_string(y);
*out = x_string;
*out += SEP;
*out += op;
*out += SEP;
*out += y_string;
#else
Json::Value root;
root["first"] = x;
root["second"] = y;
root["oper"] = op;
Json::FastWriter writer;
// Json::StyledWriter writer;
*out = writer.write(root);
#endif
return true;
}
// "x op yyyy";
bool deserialize(const std::string &in)
{
#ifdef MYSELF
// "x op y" -> 結構化
auto left = in.find(SEP);
auto right = in.rfind(SEP);
if (left == std::string::npos || right == std::string::npos)
return false;
if (left == right)
return false;
if (right - (left + SEP_LEN) != 1)
return false;
std::string x_string = in.substr(0, left); // [0, 2) [start, end) , start, end - start
std::string y_string = in.substr(right + SEP_LEN);
if (x_string.empty())
return false;
if (y_string.empty())
return false;
x = std::stoi(x_string);
y = std::stoi(y_string);
op = in[left + SEP_LEN];
#else
Json::Value root;
Json::Reader reader;
reader.parse(in, root);
x = root["first"].asInt();
y = root["second"].asInt();
op = root["oper"].asInt();
#endif
return true;
}
public:
// "x op y"
int x;
int y;
char op;
};
class Response
{
public:
Response() : exitcode(0), result(0)
{
}
Response(int exitcode_, int result_) : exitcode(exitcode_), result(result_)
{
}
bool serialize(std::string *out)
{
#ifdef MYSELF
*out = "";
std::string ec_string = std::to_string(exitcode);
std::string res_string = std::to_string(result);
*out = ec_string;
*out += SEP;
*out += res_string;
#else
Json::Value root;
root["exitcode"] = exitcode;
root["result"] = result;
Json::FastWriter writer;
*out = writer.write(root);
#endif
return true;
}
bool deserialize(const std::string &in)
{
#ifdef MYSELF
// "exitcode result"
auto mid = in.find(SEP);
if (mid == std::string::npos)
return false;
std::string ec_string = in.substr(0, mid);
std::string res_string = in.substr(mid + SEP_LEN);
if (ec_string.empty() || res_string.empty())
return false;
exitcode = std::stoi(ec_string);
result = std::stoi(res_string);
#else
Json::Value root;
Json::Reader reader;
reader.parse(in, root);
exitcode = root["exitcode"].asInt();
result = root["result"].asInt();
#endif
return true;
}
public:
int exitcode; // 0:計算成功,!0表示計算失敗,具體是多少,定好標準
int result; // 計算結果
};
// "content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op
bool recvPackage(int sock, std::string &inbuffer, std::string *text)
{
char buffer[1024];
while (true)
{
ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
inbuffer += buffer;
// 分析處理
auto pos = inbuffer.find(LINE_SEP);
if (pos == std::string::npos)
continue;
std::string text_len_string = inbuffer.substr(0, pos);
int text_len = std::stoi(text_len_string);
int total_len = text_len_string.size() + 2 * LINE_SEP_LEN + text_len;
// text_len_string + "\r\n" + text + "\r\n" <= inbuffer.size();
std::cout << "處理前#inbuffer: \n" << inbuffer << std::endl;
if (inbuffer.size() < total_len)
{
std::cout << "你輸入的消息,沒有嚴格遵守我們的協(xié)議,正在等待后續(xù)的內容, continue" << std::endl;
continue;
}
// 至少有一個完整的報文
*text = inbuffer.substr(0, total_len);
inbuffer.erase(0, total_len);
std::cout << "處理后#inbuffer:\n " << inbuffer << std::endl;
break;
}
else
return false;
}
return true;
}
makefile:文章來源地址http://www.zghlxwxcb.cn/news/detail-476253.html
cc=g++
LD=-DMYSELF
.PHONY:all
all:calserver calclient
calclient:calClient.cc
$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}
calserver:calServer.cc
$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}
.PHONY:clean
clean:
rm -f calclient calserver
到了這里,關于【網(wǎng)絡】協(xié)議定制+序列化/反序列化的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!