位置: IT常識 - 正文
推薦整理分享談?wù)刲inux網(wǎng)絡(luò)編程中的應(yīng)用層協(xié)議定制、Json序列化與反序列化那些事(linux的網(wǎng)絡(luò)編程),希望有所幫助,僅作參考,歡迎閱讀內(nèi)容。
文章相關(guān)熱門搜索詞:linux網(wǎng)絡(luò)編程采用的是哪種網(wǎng)絡(luò)字節(jié)序,linux網(wǎng)絡(luò)編程是什么,linux網(wǎng)絡(luò)編程采用的是哪種網(wǎng)絡(luò)字節(jié)序,linux網(wǎng)絡(luò)編輯,linux網(wǎng)絡(luò)編輯,linux網(wǎng)絡(luò)編輯,linux網(wǎng)絡(luò)編程用什么語言,linux的網(wǎng)絡(luò)編程,內(nèi)容如對您有幫助,希望把文章鏈接給更多的朋友!
由于socket api的接口,在讀寫數(shù)據(jù)的時候是以字符串的方式發(fā)送接收的,如果需要傳輸結(jié)構(gòu)化的數(shù)據(jù),就需要制定一個協(xié)議 結(jié)構(gòu)化數(shù)據(jù)在發(fā)送到網(wǎng)絡(luò)中之前需要完成序列化 接收方收到的是序列字節(jié)流,需要完成反序列化才能使用(如ChatInfo._name)
二、應(yīng)用層協(xié)議如何定制當(dāng)我們進(jìn)行網(wǎng)絡(luò)通信的的時候,一端發(fā)送時構(gòu)造的數(shù)據(jù), 在另一端能夠正確的進(jìn)行解析(完整的讀到一條報文), 就是可行的的. 這種約定, 就是 應(yīng)用層協(xié)議
如何保證讀到的消息是一個完整的請求 TCP是面向字節(jié)流的,無法直接讀取,需要明確報文和報文的邊界,常見的方法有.定長:固定報文長度、特殊符號:在報文前面加上一個字段、自描述。
三、網(wǎng)絡(luò)通信中數(shù)據(jù)流動的本質(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ù)據(jù)從Client的發(fā)送緩沖區(qū)拷貝到Server的接收緩沖區(qū)反過來Server->CLient:其實就是將數(shù)據(jù)從Server的發(fā)送緩沖區(qū)拷貝到 Client的接收緩沖區(qū)這也說明了網(wǎng)絡(luò)編程(套接字編程)是全雙工的四、網(wǎng)絡(luò)版計算器編寫有了前面的知識,下面實現(xiàn)一個服務(wù)器版的計算器. 我們需要客戶端把要計算的兩個數(shù)發(fā)過去, 然后由服務(wù)器進(jìn)行計算, 最后再把結(jié)果返回給客戶端
4.1 業(yè)務(wù)流程在完成服務(wù)器與客戶端正常通信的基礎(chǔ)上完成一次請求與響應(yīng)的流程 客戶端:
從鍵盤讀取數(shù)據(jù)并調(diào)用ParseLine()函數(shù)將輸入的數(shù)據(jù)轉(zhuǎn)換成類似“123+123"的格式序列化字符串,將結(jié)構(gòu)化的數(shù)據(jù)轉(zhuǎn)化成用于網(wǎng)絡(luò)通信的一個大字符串,調(diào)用請求類的序列化函數(shù)enLength()添加報頭:通過協(xié)議定制的規(guī)則,將"x op y"---->“content_len”\r\n"x op y"\r\n向服務(wù)端發(fā)送已經(jīng)構(gòu)建好的報文阻塞讀取服務(wù)端處理后的響應(yīng)數(shù)據(jù) 等待服務(wù)器發(fā)送響應(yīng)報文讀取到一個完整的報文調(diào)用協(xié)議方法去掉報頭并將結(jié)果輸出到text里面對收到的響應(yīng)正文序列化填充到響應(yīng)類對象的成員變量里通過正常調(diào)用,訪問處理后的結(jié)果發(fā)送新的請求服務(wù)端:
創(chuàng)建子進(jìn)程去執(zhí)行任務(wù)死循環(huán)式的讀取來自客戶端的報文,調(diào)用recvRequest,讀取一個完整的請求放入輸出型參數(shù)text里面根據(jù)協(xié)議定制,調(diào)用deLength()去掉報文的報頭,得到有效數(shù)據(jù)req_str將來自網(wǎng)絡(luò)的字符串通過調(diào)用請求類的反序列化轉(zhuǎn)化成結(jié)構(gòu)化的數(shù)據(jù),請求類對象成員完成賦值通過回調(diào)函數(shù)(一個輸入型參數(shù)(請求類對象),一個輸出型參數(shù)(響應(yīng)類對象))傳遞兩個類對象,將計算結(jié)果賦值給響應(yīng)類的成員變量開始將處理結(jié)果返回給客戶端,調(diào)用響應(yīng)類中的序列化方法將結(jié)構(gòu)化數(shù)據(jù)轉(zhuǎn)換成一個大字符串對大字符串添加報頭構(gòu)建成一個完整的報文,發(fā)送給客戶端服務(wù)端等待新的請求…4.2 核心代碼Protocol.hpp包含了協(xié)議定制函數(shù)、請求響應(yīng)序列化與反序列化函數(shù)、完整報文獲取函數(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é)議定制:給報文段加一個特殊字段:有效載荷的長度// 報頭 有效載荷// //"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;}// 去掉報頭,提取有效載荷//"content_len"\r\n"exitcode result"\r\n---->exitcode resultbool delength(const std::string &package, std::string *text){ auto pos = package.find(LINE_SEP); if (pos == string::npos) return false; // 提取報頭字符串 string text_len_string = package.substr(0, pos); // 將報頭信息轉(zhuǎn)化成字符串 int text_len = std::stoi(text_len_string); // 提取有效載荷 *text = package.substr(pos + LINE_SEP_LINE, text_len); return true;}// 請求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;//定義一個萬能對象 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錯誤 int result_; // 計算結(jié)果};// 讀取一個完整的請求放入text里面//"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\nbool 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); // 報頭 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; // 沒有讀到一個完整的報文 } // 至少有一個報文 *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 保證讀到的消息是【一個完整】的請求 std::string req_text; // 輸出型參數(shù),整個報文 if (!recvRequest(sock, inuffer,&req_text)) return; std::cout<<"帶報頭的請求:\n"<<req_text<<endl; // 1.2 去報頭,只要正文 std::string req_str; // 正文部分 if (!delength(req_text, &req_str)) return; std::cout<<"去掉報頭后的正文:\n"<<req_str<<endl; // 2.反序列化 // 2.1 得到一個結(jié)構(gòu)化對象,對象中的成員已經(jīng)被填充 Request req; if (!req.deserialize(req_str)) return; // 3.處理數(shù)據(jù)---------業(yè)務(wù)邏輯 // 3.1 得到一個結(jié)構(gòu)化的響應(yīng),resp成員已被填充 Response resp; func(req, resp); // 回調(diào) // 4.對響應(yīng)Response,序列化 // 4.1 得到一個字符串 std::string resp_str; resp.serialize(&resp_str); // 輸出型參數(shù),將序列化結(jié)果寫入resp_str std::cout<<"計算完成,序列化響應(yīng): "<<resp_str<<endl; // 5.然后發(fā)送響應(yīng) // 5.1添加協(xié)議報頭,構(gòu)建成一個完整的報文 std::string send_string = enlength(resp_str); std::cout<<"構(gòu)建帶報頭的響應(yīng)正文: \n"<<send_string<<endl; // 發(fā)送 send(sock, send_string.c_str(), send_string.size(), 0); // 有問題 std::cout<<"發(fā)送響應(yīng)報文成功: \n"<<endl; } }客戶端請求流程
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); // 添加報頭 string send_string = enlength(content); // 發(fā)送數(shù)據(jù) send(sockfd_, send_string.c_str(), send_string.size(), 0); // 接收響應(yīng)報文 string package, text; if (!recvRequest(sockfd_,inbuffer,&package)) continue; //去掉報頭,獲取正文放在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; } } }正常輸入輸出顯示如下圖
以上只是提供了幾個核心的代碼塊,完整版代碼可以去我的Gitee,代碼注釋詳細(xì),希望對你有所幫助
上一篇:新河峽國家公園中的新河峽大橋,西弗吉尼亞州 (? Entropy Workshop/iStock/Getty Images Plus)(教案的近義詞是什么)
下一篇:vue中props設(shè)置默認(rèn)值-父組件給子組件傳值的寫法——簡略、帶類型、帶類型和默認(rèn)值、帶校驗(vue props emit)
網(wǎng)站地圖: 企業(yè)信息 工商信息 財稅知識 網(wǎng)絡(luò)常識 編程技術(shù)
友情鏈接: 武漢網(wǎng)站建設(shè)