一、序列化與反序列化
由于socket api的接口,在讀寫數(shù)據(jù)的時(shí)候是以字符串的方式發(fā)送接收的,如果需要傳輸結(jié)構(gòu)化的數(shù)據(jù),就需要制定一個(gè)協(xié)議
結(jié)構(gòu)化數(shù)據(jù)在發(fā)送到網(wǎng)絡(luò)中之前需要完成序列化
接收方收到的是序列字節(jié)流,需要完成反序列化才能使用(如ChatInfo._name)
二、應(yīng)用層協(xié)議如何定制
當(dāng)我們進(jìn)行網(wǎng)絡(luò)通信的的時(shí)候,一端發(fā)送時(shí)構(gòu)造的數(shù)據(jù), 在另一端能夠正確的進(jìn)行解析(完整的讀到一條報(bào)文), 就是可行的的. 這種約定, 就是 應(yīng)用層協(xié)議
如何保證讀到的消息是一個(gè)完整的請(qǐng)求
TCP是面向字節(jié)流的,無法直接讀取,需要明確報(bào)文和報(bào)文的邊界,常見的方法有.定長:固定報(bào)文長度、特殊符號(hào):在報(bào)文前面加上一個(gè)字段、自描述。
三、網(wǎng)絡(luò)通信中數(shù)據(jù)流動(dòng)的本質(zhì)
我們調(diào)用的所有的發(fā)送函數(shù)(read),不是把數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)中,發(fā)送函數(shù)的本質(zhì)是拷貝函數(shù)(將數(shù)據(jù)從應(yīng)用層緩沖區(qū)拷貝到發(fā)送緩沖區(qū))
- Client->Server:tcp發(fā)送的本質(zhì),其實(shí)就是將數(shù)據(jù)從Client的發(fā)送緩沖區(qū)拷貝到Server的接收緩沖區(qū)
- 反過來Server->CLient:其實(shí)就是將數(shù)據(jù)從Server的發(fā)送緩沖區(qū)拷貝到
Client的接收緩沖區(qū) - 這也說明了網(wǎng)絡(luò)編程(套接字編程)是全雙工的
四、網(wǎng)絡(luò)版計(jì)算器編寫
有了前面的知識(shí),下面實(shí)現(xiàn)一個(gè)服務(wù)器版的計(jì)算器. 我們需要客戶端把要計(jì)算的兩個(gè)數(shù)發(fā)過去, 然后由服務(wù)器進(jìn)行計(jì)算, 最后再把結(jié)果返回給客戶端
4.1 業(yè)務(wù)流程
在完成服務(wù)器與客戶端正常通信的基礎(chǔ)上完成一次請(qǐng)求與響應(yīng)的流程
客戶端:
- 從鍵盤讀取數(shù)據(jù)并調(diào)用ParseLine()函數(shù)將輸入的數(shù)據(jù)轉(zhuǎn)換成類似
“123+123"
的格式 - 序列化字符串,將結(jié)構(gòu)化的數(shù)據(jù)轉(zhuǎn)化成用于網(wǎng)絡(luò)通信的一個(gè)大字符串,調(diào)用請(qǐng)求類的序列化函數(shù)enLength()
- 添加報(bào)頭:通過協(xié)議定制的規(guī)則,將"x op y"---->“content_len”\r\n"x op y"\r\n
- 向服務(wù)端發(fā)送已經(jīng)構(gòu)建好的報(bào)文
- 阻塞讀取服務(wù)端處理后的響應(yīng)數(shù)據(jù)
等待服務(wù)器發(fā)送響應(yīng)報(bào)文 - 讀取到一個(gè)完整的報(bào)文
- 調(diào)用協(xié)議方法去掉報(bào)頭并將結(jié)果輸出到text里面
- 對(duì)收到的響應(yīng)正文序列化填充到響應(yīng)類對(duì)象的成員變量里
- 通過正常調(diào)用,訪問處理后的結(jié)果
- 發(fā)送新的請(qǐng)求
服務(wù)端:
- 創(chuàng)建子進(jìn)程去執(zhí)行任務(wù)
- 死循環(huán)式的讀取來自客戶端的報(bào)文,調(diào)用recvRequest,讀取一個(gè)完整的請(qǐng)求放入輸出型參數(shù)text里面
- 根據(jù)協(xié)議定制,調(diào)用deLength()去掉報(bào)文的報(bào)頭,得到有效數(shù)據(jù)req_str
- 將來自網(wǎng)絡(luò)的字符串通過調(diào)用請(qǐng)求類的反序列化轉(zhuǎn)化成結(jié)構(gòu)化的數(shù)據(jù),請(qǐng)求類對(duì)象成員完成賦值
- 通過回調(diào)函數(shù)(一個(gè)輸入型參數(shù)(請(qǐng)求類對(duì)象),一個(gè)輸出型參數(shù)(響應(yīng)類對(duì)象))傳遞兩個(gè)類對(duì)象,將計(jì)算結(jié)果賦值給響應(yīng)類的成員變量
- 開始將處理結(jié)果返回給客戶端,調(diào)用響應(yīng)類中的序列化方法將結(jié)構(gòu)化數(shù)據(jù)轉(zhuǎn)換成一個(gè)大字符串
- 對(duì)大字符串添加報(bào)頭構(gòu)建成一個(gè)完整的報(bào)文,發(fā)送給客戶端
- 服務(wù)端等待新的請(qǐng)求…
4.2 核心代碼
Protocol.hpp包含了協(xié)議定制函數(shù)、請(qǐng)求響應(yīng)序列化與反序列化函數(shù)、完整報(bào)文獲取函數(shù)
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <jsoncpp/json/json.h>
using namespace std;
#define SEP " " // 分隔符
#define SEP_LEN strlen(SEP) // 分隔符長度,不能用sizeof
#define LINE_SEP "\r\n"
#define LINE_SEP_LINE strlen(LINE_SEP)
enum
{
OK = 0,
DIV_ZERO,
MOD_ZERO,
OP_ERROR
};
// 協(xié)議定制:給報(bào)文段加一個(gè)特殊字段:有效載荷的長度
// 報(bào)頭 有效載荷
//
//"exitcode result"---->"content_len"\r\n"exitcode result"\r\n----
std::string enlength(const std::string &text)
{
// text就是"x op y"
string send_string = std::to_string(text.size()); // content_len
send_string += LINE_SEP;
send_string += text;
send_string += LINE_SEP;
return send_string;
}
// 去掉報(bào)頭,提取有效載荷
//"content_len"\r\n"exitcode result"\r\n---->exitcode result
bool delength(const std::string &package, std::string *text)
{
auto pos = package.find(LINE_SEP);
if (pos == string::npos)
return false;
// 提取報(bào)頭字符串
string text_len_string = package.substr(0, pos);
// 將報(bào)頭信息轉(zhuǎn)化成字符串
int text_len = std::stoi(text_len_string);
// 提取有效載荷
*text = package.substr(pos + LINE_SEP_LINE, text_len);
return true;
}
// 請(qǐng)求
class Request
{
public:
Request()
: x_(0), y_(0), op_(0)
{
}
Request(int x, int y, char op)
: x_(x), y_(y), op_(op)
{
}
// 序列化
bool serialize(std::string *out)
{
#ifdef MYSELF
// 將結(jié)構(gòu)化數(shù)據(jù)轉(zhuǎn)化成-->"x op y"
out->clear();
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;//定義一個(gè)萬能對(duì)象
root["first"]=x_;
root["second"]=y_;
root["oper"]=op_;
Json::FastWriter writer;
*out=writer.write(root);
#endif
return true;
}
// 反序列化
bool deserialize(const std::string &in)
{
#ifdef MYSELF
//"x op yyy";
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); // 定位到x
std::string y_string = in.substr(right + SEP_LEN); // 定位到y(tǒng)yyy
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]; // 截取op
#else
Json::Value root;
Json::Reader reader;
reader.parse(in,root);//將解析出來的值放進(jìn)root里面
x_=root["first"].asInt();//將val轉(zhuǎn)化成整數(shù)
y_=root["second"].asInt();
op_=root["oper"].asInt();
#endif
return true;
}
public:
int x_;
int y_;
char op_;
};
// 響應(yīng)
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->clear();
// 將退出碼和結(jié)果轉(zhuǎn)換成字符串
string ec_string = std::to_string(exitcode_);
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;
// 截取字符串
string ec_string = in.substr(0, mid);
string res_string = in.substr(mid + SEP_LEN);
if (ec_string.empty() || res_string.empty())
return false;
// 寫入退出碼和結(jié)果
exitcode_ = std::stoi(ec_string);
result_ = std::stoi(res_string);
#else
Json::Reader reader;
Json::Value root;
reader.parse(in,root);
exitcode_=root["exitcode"].asInt();
result_=root["result"].asInt();
#endif
return true;
}
public:
int exitcode_; // 0成功,!0錯(cuò)誤
int result_; // 計(jì)算結(jié)果
};
// 讀取一個(gè)完整的請(qǐng)求放入text里面
//"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\n
bool recvRequest(int sock, std::string &inbuffer, 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);
// 如果沒有讀到\r\n,接著去讀
if (pos == string::npos)
continue;
// 走到這已經(jīng)讀到了content_len,知道了有效載荷長度
string text_len_string = inbuffer.substr(0, pos); // 報(bào)頭
int text_len = std::stoi(text_len_string); // 正文長度
int total_len = text_len_string.size() + 2 * LINE_SEP_LINE + text_len;
std::cout << "處理前#inbuffer: \n"
<< inbuffer << endl;
if (inbuffer.size() < total_len)
{
std::cout << "你輸入的消息,沒有嚴(yán)格遵守我們的協(xié)議,正在等待后續(xù)的內(nèi)容, continue" << std::endl;
continue; // 沒有讀到一個(gè)完整的報(bào)文
}
// 至少有一個(gè)報(bào)文
*text = inbuffer.substr(0, total_len);
inbuffer.erase(0, total_len);
std::cout << "處理后#inbuffer: \n"
<< inbuffer << endl;
break;
}
else
return false;
}
return true;
}
服務(wù)端響應(yīng)流程
void handlerEnter(int sock, func_t func)
{
string inuffer;//將所有信息寫入到inbuffer
while(true)
{
// 1.讀?。?content_len"\r\n"x op y"\r\n
// 1.1 保證讀到的消息是【一個(gè)完整】的請(qǐng)求
std::string req_text; // 輸出型參數(shù),整個(gè)報(bào)文
if (!recvRequest(sock, inuffer,&req_text))
return;
std::cout<<"帶報(bào)頭的請(qǐng)求:\n"<<req_text<<endl;
// 1.2 去報(bào)頭,只要正文
std::string req_str; // 正文部分
if (!delength(req_text, &req_str))
return;
std::cout<<"去掉報(bào)頭后的正文:\n"<<req_str<<endl;
// 2.反序列化
// 2.1 得到一個(gè)結(jié)構(gòu)化對(duì)象,對(duì)象中的成員已經(jīng)被填充
Request req;
if (!req.deserialize(req_str))
return;
// 3.處理數(shù)據(jù)---------業(yè)務(wù)邏輯
// 3.1 得到一個(gè)結(jié)構(gòu)化的響應(yīng),resp成員已被填充
Response resp;
func(req, resp); // 回調(diào)
// 4.對(duì)響應(yīng)Response,序列化
// 4.1 得到一個(gè)字符串
std::string resp_str;
resp.serialize(&resp_str); // 輸出型參數(shù),將序列化結(jié)果寫入resp_str
std::cout<<"計(jì)算完成,序列化響應(yīng): "<<resp_str<<endl;
// 5.然后發(fā)送響應(yīng)
// 5.1添加協(xié)議報(bào)頭,構(gòu)建成一個(gè)完整的報(bào)文
std::string send_string = enlength(resp_str);
std::cout<<"構(gòu)建帶報(bào)頭的響應(yīng)正文: \n"<<send_string<<endl;
// 發(fā)送
send(sock, send_string.c_str(), send_string.size(), 0); // 有問題
std::cout<<"發(fā)送響應(yīng)報(bào)文成功: \n"<<endl;
}
}
客戶端請(qǐng)求流程
void run()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(clientport_);
server.sin_addr.s_addr = inet_addr(clientip_.c_str());
// 發(fā)起鏈接
if (connect(sockfd_, (struct sockaddr *)&server, sizeof(server)) != 0)
{
std::cerr << "connect create error" << endl;
}
else
{
string msg;
string inbuffer;
while (true)
{
cout << "mycal>>> ";
std::getline(std::cin, msg);
Request req = ParseLine(msg); // 從鍵盤提取字符串
string content;
// 序列化結(jié)構(gòu)數(shù)據(jù)
req.serialize(&content);
// 添加報(bào)頭
string send_string = enlength(content);
// 發(fā)送數(shù)據(jù)
send(sockfd_, send_string.c_str(), send_string.size(), 0);
// 接收響應(yīng)報(bào)文
string package, text;
if (!recvRequest(sockfd_,inbuffer,&package))
continue;
//去掉報(bào)頭,獲取正文放在text里面
if(!delength(package,&text)) continue;
//將收到的響應(yīng)正文反序列化
Response resp;
resp.deserialize(text);
std::cout << "exitCode: " << resp.exitcode_ << std::endl;
std::cout << "result: " << resp.result_ << std::endl;
}
}
}
正常輸入輸出顯示如下圖文章來源:http://www.zghlxwxcb.cn/news/detail-459830.html
以上只是提供了幾個(gè)核心的代碼塊,完整版代碼可以去我的Gitee,代碼注釋詳細(xì),希望對(duì)你有所幫助文章來源地址http://www.zghlxwxcb.cn/news/detail-459830.html
到了這里,關(guān)于談?wù)刲inux網(wǎng)絡(luò)編程中的應(yīng)用層協(xié)議定制、Json序列化與反序列化那些事的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!