Linux中的自定義協(xié)議+序列反序列化用法
我們程序員寫的一個個解決我們實際問題, 滿足我們?nèi)粘P枨蟮木W(wǎng)絡(luò)程序, 都是在應(yīng)用層.
一,再次理解協(xié)議
思考1: 我們究竟是如何將數(shù)據(jù)發(fā)送出去的?
(1)我們都知道TCP是雙加工的,所以在內(nèi)核中存在著發(fā)送緩沖區(qū)和接受緩沖區(qū),而我們write是寫入發(fā)送緩沖區(qū)的,read是從接受緩沖區(qū)里讀的,所以我們會發(fā)現(xiàn)這兩個函數(shù)其實就是拷貝函數(shù)!!write將數(shù)據(jù)從用戶層拷貝到內(nèi)核層就返回,read將數(shù)據(jù)從內(nèi)核層拷貝到用戶層就返回,意思就是我用戶不管,反正我把數(shù)據(jù)都交給你OS了,發(fā)送過程中的可靠性由你來維護(hù)。(就像當(dāng)年我們只需要將內(nèi)容寫到文件內(nèi)核緩沖區(qū),而由OS來決定什么時候,以什么方式刷新到磁盤上)
(2)所以TCP協(xié)議是屬于操作系統(tǒng)的網(wǎng)絡(luò)模塊部分,他之所以叫做傳輸控制協(xié)議,就是因為他需要確保數(shù)據(jù)傳輸過程中的可靠性,比方說我什么時候應(yīng)該發(fā)給對方?要發(fā)多少?萬一出錯了怎么辦?
思考2:回憶管道和文件系統(tǒng)
(1)以往我們往管道文件里寫了很多次的時候,可能我們一次就全部讀上來了
(2)而在文件系統(tǒng)中,我們的寫很容易,可以分很多次寫,各種類型比如整數(shù)/字符串…… 但是讀的時候就很難去讀,同時不同的文件的讀取方式可能也不一樣,比如按行讀也僅僅只是讀取文件的一種方式而已。
思考3:TCP是面向字節(jié)流的,你怎么保證你讀上來的數(shù)據(jù)一定是一個"完整的報文"呢?
(1)早期的時候我們可能會想到比方說我們約定必須要湊齊多少字節(jié)才往上讀,但是這個其實只適用于一些固定類型的報文,比方說我們規(guī)定讀的是int,而int對應(yīng)的就是一個錯誤碼或者是某一個固定的任務(wù)。但是如果是一些長度不固定的報文,比如說我們在聊天的時候發(fā)送的字符串長度都是不一樣的,那么這個時候就很容易讀到不完整的報文
(2)所以為了應(yīng)對這種情況,我們就需要在報文直接加入一下分割符,或者是標(biāo)識這個報文有多長,以確保能夠讀到完整的報文, 而協(xié)議要添加報頭就會有很多新得問題出來-比如序列化和反序列化。
二,序列化和反序列化
問題1:協(xié)議是一種 "約定". socket api的接口, 在讀寫數(shù)據(jù)時, 都是按 "字符串" 的方式來發(fā)送接收的. 如果我們要傳輸一些"結(jié)構(gòu)化的數(shù)據(jù)" 怎么辦呢?
(1)同一個結(jié)構(gòu)體在不同的編譯器下編譯的大小不一定一樣(結(jié)構(gòu)體的內(nèi)存對齊),其實Linux的協(xié)議就是傳結(jié)構(gòu)體做到的,但是他底層可以把所有方方面面的情況都考慮到了,但是我們用戶去定的時候很難考慮得這么周全。
(2) 可以如果不用結(jié)構(gòu)體的話,我們?nèi)绻米址??舉個例子,我們聊天的時候我發(fā)了一句哈哈,但是發(fā)送的時候還會帶上昵稱以及發(fā)送時間,所以我們肯定不能把這三個字符串分開發(fā),因為這也服務(wù)端就不知道你這話是誰說的,所以我肯定希望把三個字符串打包成一個字符串發(fā)過去,然后服務(wù)的把消息廣播給所有客戶端的時候,會再把這個包根據(jù)一定的方法解析分成三個字符串然后再給你顯現(xiàn)出來!------------->也就是說我們需要在類里面定義兩種方法,一種是把類內(nèi)的數(shù)據(jù)打包成一個字符串發(fā)送過去,另一種是解析的時候?qū)⑦@個字符串再拆分出來。 而這個過程就是序列化和反序列化
問題2 :我們需要實現(xiàn)一個服務(wù)器版的加法器. 我們需要客戶端把要計算的兩個加數(shù)發(fā)過去, 然后由服務(wù)器進(jìn)行計算, 最 后再把結(jié)果返回給客戶端.
約定方案一:
- 客戶端發(fā)送一個形如"1+1"的字符串;
- 這個字符串中有兩個操作數(shù), 都是整形;
- 兩個數(shù)字之間會有一個字符是運算符, 運算符只能是 + ;
- 數(shù)字和運算符之間沒有空格;
- ...
約定方案二:
定義結(jié)構(gòu)體來表示我們需要交互的信息;
發(fā)送數(shù)據(jù)時將這個結(jié)構(gòu)體按照一個規(guī)則轉(zhuǎn)換成字符串, 接收到數(shù)據(jù)的時候再按照相同的規(guī)則把字符串轉(zhuǎn) 化回結(jié)構(gòu)體; 這個過程叫做 "序列化" 和”反序列化“
三,實現(xiàn)網(wǎng)絡(luò)計算器
3.1 日志文件
#pragma once #include <iostream> #include <time.h> #include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #define SIZE 1024 #define Info 0 #define Debug 1 #define Warning 2 #define Error 3 #define Fatal 4 #define Screen 1 #define Onefile 2 #define Classfile 3 #define LogFile "log.txt" class Log { public: Log() { printMethod = Screen; path = "./log/"; } void Enable(int method) { printMethod = method; } std::string levelToString(int level) { switch (level) { case Info: return "Info"; case Debug: return "Debug"; case Warning: return "Warning"; case Error: return "Error"; case Fatal: return "Fatal"; default: return "None"; } } // void logmessage(int level, const char *format, ...) // { // time_t t = time(nullptr); // struct tm *ctime = localtime(&t); // char leftbuffer[SIZE]; // snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), // ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, // ctime->tm_hour, ctime->tm_min, ctime->tm_sec); // // va_list s; // // va_start(s, format); // char rightbuffer[SIZE]; // vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); // // va_end(s); // // 格式:默認(rèn)部分+自定義部分 // char logtxt[SIZE * 2]; // snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer); // // printf("%s", logtxt); // 暫時打印 // printLog(level, logtxt); // } void printLog(int level, const std::string &logtxt) { switch (printMethod) { case Screen: std::cout << logtxt << std::endl; break; case Onefile: printOneFile(LogFile, logtxt); break; case Classfile: printClassFile(level, logtxt); break; default: break; } } void printOneFile(const std::string &logname, const std::string &logtxt) { std::string _logname = path + logname; int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt" if (fd < 0) return; write(fd, logtxt.c_str(), logtxt.size()); close(fd); } void printClassFile(int level, const std::string &logtxt) { std::string filename = LogFile; filename += "."; filename += levelToString(level); // "log.txt.Debug/Warning/Fatal" printOneFile(filename, logtxt); } ~Log() { } void operator()(int level, const char *format, ...) { time_t t = time(nullptr); struct tm *ctime = localtime(&t); char leftbuffer[SIZE]; snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec); va_list s; va_start(s, format); char rightbuffer[SIZE]; vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); va_end(s); // 格式:默認(rèn)部分+自定義部分 char logtxt[SIZE * 2]; snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer); // printf("%s", logtxt); // 暫時打印 printLog(level, logtxt); } private: int printMethod; std::string path; }; // int sum(int n, ...) // { // va_list s; // char* // va_start(s, n); // int sum = 0; // while(n) // { // sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123); // n--; // } // va_end(s); //s = NULL // return sum; // } Log lg; // 命令對象 用來打印日志信息
3.2Socket.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 <string.h> #include "Log.hpp" enum { SocketError = 1, BindError =2, ListenError = 3, AcceptError = 4 }; const int defaultbacklog = 10;//監(jiān)聽隊列默認(rèn)長度 class Sock { public: Sock(){} ~Sock(){} void Socket()//創(chuàng)建套接字 { _sockfd = socket(AF_INET, SOCK_STREAM, 0); if (_sockfd<0) { lg(Fatal,"socket error,%s:%d",strerror(errno),errno); exit(SocketError); } } void Bind(uint16_t port)//綁定套接字 { struct sockaddr_in addr; bzero(&addr, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; if(bind(_sockfd, (struct sockaddr*)&addr, sizeof(addr))<0) { lg(Fatal,"bind error,%s:%d",strerror(errno),errno); exit(BindError); } } void Listen(int backlog=defaultbacklog)//監(jiān)聽套接字 { if(listen(_sockfd, backlog)<0) { lg(Fatal,"listen error,%s:%d",strerror(errno),errno); exit(ListenError); } } int Accept(std::string *clientip,uint16_t *clientport)//接受套接字并把客戶端的ip和端口返回(輸出型參數(shù)) { struct sockaddr_in client; socklen_t len = sizeof(client); int connfd = accept(_sockfd, (struct sockaddr*)&client, &len); if(connfd<0) { lg(Error,"accept error,%s:%d",strerror(errno),errno);//如果接受失敗 打印錯誤信息并退出程序 exit(AcceptError); } char ipstr[64]; inet_ntop(AF_INET, &client.sin_addr,ipstr, sizeof(ipstr)); *clientip = ipstr; *clientport = ntohs(client.sin_port); lg(Info,"accept a client %s:%d",*clientip,*clientport);//表示接受成功了并打印客戶端的ip和端口 return connfd; } int GetSockfd()//獲取套接字 { return _sockfd; } void Close()//關(guān)閉套接字 { close(_sockfd); } bool Connect(const std::string &ip,const uint16_t &port)//連接套接字 { struct sockaddr_in addr; bzero(&addr, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_pton(AF_INET, ip.c_str(), &addr.sin_addr) ;//將ip地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序 if(connect(_sockfd, (struct sockaddr*)&addr, sizeof(addr))<0) { lg(Warning,"connect error,%s:%d",strerror(errno),errno); return false; } return true; } private: int _sockfd; };
3.3 TcpServer.hpp
#pragma once #include <functional> #include <signal.h> #include "Socket.hpp" using func_t = std::function<std::string(std::string &package)>; // 回調(diào)函數(shù) class TcpServer { public: TcpServer(uint16_t port, func_t callback) : _port(port), _callback(callback), _isrunning(false) { } ~TcpServer() {} void InitServer() { _listensock.Socket(); _listensock.Bind(_port); _listensock.Listen(10); lg(Info, "Server is running on port %d", _port); // 看看服務(wù)器在哪個端口上運行 } void Start() // 啟動服務(wù)器 { _isrunning = true; signal(SIGCHLD, SIG_IGN); // 忽略子進(jìn)程結(jié)束信號 因為子進(jìn)程結(jié)束時會產(chǎn)生SIGCHLD信號 signal(SIGPIPE, SIG_IGN); // 忽略管道錯誤信號 因為當(dāng)網(wǎng)絡(luò)連接中某個套接字被關(guān)閉或者重啟時,該套接字已經(jīng)發(fā)送緩沖區(qū)中的數(shù)據(jù)都發(fā)送完畢了,但是它仍然可以接收數(shù)據(jù) 此時該套接字就會產(chǎn)生SIGPIPE信號 while (_isrunning) { std::string clientip; uint16_t clientport; int sockfd = _listensock.Accept(&clientip, &clientport); // 接受客戶端連接并返回套接字描述符 if (sockfd < 0) continue; // 連接失敗就繼續(xù)嘗試重連 pid_t pid = fork(); // 創(chuàng)建子進(jìn)程 幫助我們處理每個客戶端的請求 if (pid < 0) // 出錯 { lg(Error, "Fork error"); continue; } if (pid == 0) // 子進(jìn)程 { _listensock.Close(); // 關(guān)閉監(jiān)聽套接字 防止accept阻塞 std::string inbuffer_stream; // 用來獲取客戶端發(fā)來的所有數(shù)據(jù) while (true) { char buffer[1024]; ssize_t n = read(sockfd, buffer, sizeof(buffer)); // 讀取客戶端數(shù)據(jù) if (n == 0) // 客戶端斷開了 { lg(Info, "Client %s:%d disconnected", clientip.c_str(), clientport); break; } if (n < 0) // 讀取出錯 { lg(Error, "Read error"); break; } // 拼接所有數(shù)據(jù) buffer[n]=0; inbuffer_stream+=buffer; lg(Debug, "debug:\n%s", inbuffer_stream.c_str());// 調(diào)試看看整個流的信息 //有可能一個流里面有多個報文,所以我們要循環(huán)去處理 while(1) { std::string info =_callback(inbuffer_stream);// 調(diào)用回調(diào)函數(shù)獲取服務(wù)端需要的數(shù)據(jù) //看看剩余的報文信息 if(info.empty()) break;//說明讀不到完整報文了 lg(Debug, "debug:\n%s", inbuffer_stream.c_str());// 調(diào)試看看整個流的信息 lg(Debug, "debug,response:\n%s",info.c_str());// 調(diào)試看看發(fā)給客戶端數(shù)據(jù) write(sockfd, info.c_str(), info.size()); // 發(fā)送數(shù)據(jù)給客戶端 } } exit(0); // 子進(jìn)程結(jié)束 } close(sockfd); // 關(guān)閉套接字 } } private: uint16_t _port; // 端口號 Sock _listensock; // 監(jiān)聽套接字 func_t _callback; // 回調(diào)函數(shù) 用來提供服務(wù) // ip模式是0 bool _isrunning; // 是否運行 };
1、tcpserver只負(fù)責(zé)網(wǎng)絡(luò)通信讀到了那些數(shù)據(jù),但是關(guān)于數(shù)據(jù)的解析 全部交給callback回調(diào)函數(shù)去處理這樣就是將網(wǎng)絡(luò)通信和協(xié)議解析進(jìn)行了解耦
2、因為我們并不確定讀到的是否是一個完整報文,所以我們要將讀到的內(nèi)容加到inbuffer-stream里
3.4 Protocol.hpp
協(xié)議其實就是我們雙方約定好的結(jié)構(gòu)化字段
為了確保讀到完整的報文,在前面加個有關(guān)報文長度的字段是是一種方案,在報文的后面加個分割符\其實也是一個方案
甚至我們可以在前面增加protocol select字段,表示我們選擇的不同的協(xié)議類型!
- "len"/n"x op y"/n就是我們約定request的協(xié)議,里面要提供序列化和反序列化的方法
- ”len“/n"result code"/n 就是我們約定的respose的協(xié)議里面要提供序列化和反序列化的方法
#pragma once #include <iostream> #include <string> // #define MySelf 1 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) { std::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); // package = len_str + content_str + 2 std::size_t total_len = len_str.size() + len + 2; if(package.size() < total_len) return false; *content = package.substr(pos+1, len); // earse 移除報文 package.erase(0, total_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) { // 構(gòu)建報文的有效載荷 // 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 + 1); if (left + 2 != right) return false; _op = in[left + 1]; _x = std::stoi(part_x); _y = std::stoi(part_y); return true; } void DebugPrint() { std::cout << "新請求構(gòu)建完成: " << _x << _op << _y << "=?" << std::endl; } public: // x op y int _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)建報文的有效載荷 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) // "result code" { 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,可信,否則!0具體是幾,表明對應(yīng)的錯誤原因 };
不僅需要有序列化和反序列化的方法,還要有添加報頭和解析報頭(要有很多檢查 將一個有效的報文提取出來)的方法
3.5 Servercal.hpp
// 計算器服務(wù) #include <iostream> #include "Protocol.hpp" //協(xié)議頭文件 必須遵守 enum { Div_Zero = 1, Mod_Zero = 2, Other_Oper = 3 }; class ServerCal { public: ServerCal() {} ~ServerCal() {} Response Calhelper(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._code = Div_Zero; else resp._result = req._x / req._y; break; case '%': if (req._y == 0) resp._code = Mod_Zero; else resp._result = req._x % req._y; break; default: resp._code=Other_Oper; break; } return resp; } //設(shè)計一個回調(diào)方法 來幫助我們計算 std::string Cal(std::string package) //回調(diào)方法 到時傳過去 { std::string content;//返回的內(nèi)容 bool r=Decode(package,&content); if(!r) return "";//報文不完整 //我們要將這個報文反序列化拿到數(shù)據(jù) 然后計算成respond 然后再序列化發(fā)給客戶端 Request req; r=req.Deserialize(content); //"10 + 20 " -> if(!r) return ""; //解析失敗 content=""; //清空再利用 Response resp=Calhelper(req); resp.Serialize(&content); content=Encode(content);//把報頭加上去 return content; } };
在這里寫一個回調(diào)方法,如果解析失敗的話就返回空串
3.6 ServerCal.cc
#include"ServerCal.hpp" #include"TcpServer.hpp" static void Usage(const std::string &proc) { std::cout << "\nUsage: " << proc << " port\n" << std::endl; } int main(int argc, char *argv[])//./servercal 8080 { if(argc != 2) { Usage(argv[0]); exit(0); } uint16_t port = atoi(argv[1]); ServerCal cal;//用來做計算請求的對象 TcpServer *tsvp = new TcpServer(port,std::bind(&ServerCal::Cal, &cal, std::placeholders::_1));//bind tsvp->InitServer(); // Daemon(); daemon(0, 0); tsvp->Start(); return 0; }
3.7ClientCal.cc
#include <iostream> #include <string> #include <ctime> #include <cassert> #include <unistd.h> #include "Socket.hpp" #include "Protocol.hpp" //客戶端也得知道協(xié)議 static void Usage(const std::string &proc) { std::cout << "\nUsage: " << proc << " serverip serverport\n" << std::endl; } //./client 127.0.0.1 8080 int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); return -1; } std::string serverip = argv[1]; uint16_t serverport = std::stoi(argv[2]); Sock sockfd; sockfd.Socket(); bool r=sockfd.Connect(serverip, serverport);//嘗試和服務(wù)端連接 if(!r) { std::cout<<"連接失敗"<<std::endl; return -1; } //否則就是連接成功 srand(time(nullptr) ^ getpid()); int cnt = 1; const std::string opers = "+-*/%=-=&^"; std::string inbuffer_stream; while(cnt <= 10) { std::cout << "===============第" << cnt << "次測試....., " << "===============" << std::endl; int x = rand() % 100 + 1; usleep(1234); int y = rand() % 100; usleep(4321); char oper = opers[rand()%opers.size()]; Request req(x, y, oper); req.DebugPrint(); std::string package; req.Serialize(&package); package = Encode(package); write(sockfd.GetSockfd(), package.c_str(), package.size()); // std::cout << "這是最新的發(fā)出去的請求: " << n << "\n" << package; // n = write(sockfd.Fd(), package.c_str(), package.size()); // std::cout << "這是最新的發(fā)出去的請求: \n" << n << "\n" << package; // n = write(sockfd.Fd(), package.c_str(), package.size()); // std::cout << "這是最新的發(fā)出去的請求: \n" << n << "\n" << package; // n = write(sockfd.Fd(), package.c_str(), package.size()); // std::cout << "這是最新的發(fā)出去的請求: \n" << n << "\n" << package; char buffer[128]; ssize_t n = read(sockfd.GetSockfd(), buffer, sizeof(buffer)); // 我們也無法保證我們能讀到一個完整的報文 if(n > 0) { buffer[n] = 0; inbuffer_stream += buffer; // "len"\n"result code"\n std::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; }
3.8 Daemon.hpp
#pragma once #include <iostream> #include <cstdlib> #include <unistd.h> #include <signal.h> #include <string> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> const std::string nullfile = "/dev/null"; void Daemon(const std::string &cwd = "") { // 1. 忽略其他異常信號 signal(SIGCLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); signal(SIGSTOP, SIG_IGN); // 2. 將自己變成獨立的會話 if (fork() > 0) exit(0); setsid(); // 3. 更改當(dāng)前調(diào)用進(jìn)程的工作目錄 if (!cwd.empty()) chdir(cwd.c_str()); // 4. 標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)錯誤重定向至/dev/null int fd = open(nullfile.c_str(), O_RDWR); if(fd > 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } }
3.9 json簡單使用
其實我們有更好的兩個工具能幫我們完成序列化和反序列化,一個是json 一個是productor
- 關(guān)于json的下載
安裝這個json,其實就是將頭文件和源文件安裝在制定的路徑下
- 頭文件:
- 庫文件:
- 使用第三方庫必須要指定鏈接
測試代碼:
#include <iostream> #include <jsoncpp/json/json.h> #include <unistd.h> // {a:120, b:"123"} int main() { Json::Value part1; part1["haha"] = "haha"; part1["hehe"] = "hehe"; Json::Value root; root["x"] = 100; root["y"] = 200; root["op"] = '+'; root["desc"] = "this is a + oper"; root["test"] = part1; //Json::FastWriter w; Json::StyledWriter w; std::string res = w.write(root); std::cout << res << std::endl; sleep(3); Json::Value v; Json::Reader r; r.parse(res, v); int x = v["x"].asInt(); int y = v["y"].asInt(); char op = v["op"].asInt(); std::string desc = v["desc"].asString(); Json::Value temp = v["test"]; std::cout << x << std::endl; std::cout << y << std::endl; std::cout << op << std::endl; std::cout << desc << std::endl; return 0; }
(1)頭文件必須要指明路徑
(2)Json是萬能類 Value代表對象
(3)FastWrite是快速寫StyleWrite是風(fēng)格寫
(4)asInt表示解析成int類型
3.10Protocol.hpp改進(jìn)版
#pragma once #include <iostream> #include <string> #include<jsoncpp/json/json.h> // #define MySelf 1 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) { std::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); // package = len_str + content_str + 2 std::size_t total_len = len_str.size() + len + 2; if(package.size() < total_len) return false; *content = package.substr(pos+1, len); // earse 移除報文 package.erase(0, total_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)建報文的有效載荷 // 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; #else Json::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 MySelf 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 + 1); if (left + 2 != right) return false; _op = in[left + 1]; _x = std::stoi(part_x); _y = std::stoi(part_y); return true; #else Json::Value root; Json::Reader r; r.parse(in, root); _x = root["x"].asInt(); _y = root["y"].asInt(); _op = root["op"].asInt(); return true; #endif } void DebugPrint() { std::cout << "新請求構(gòu)建完成: " << _x << _op << _y << "=?" << std::endl; } public: // x op y int _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)建報文的有效載荷 std::string s = std::to_string(_result); s += blank_space_sep; s += std::to_string(_code); *out = s; return true; #else Json::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) // "result code" { #ifdef MySelf 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; #else Json::Value root; Json::Reader r; r.parse(in, root); _result = root["result"].asInt(); _code = root["code"].asInt(); return true; #endif } void DebugPrint() { std::cout << "結(jié)果響應(yīng)完成, result: " << _result << ", code: "<< _code << std::endl; } public: int _result; int _code; // 0,可信,否則!0具體是幾,表明對應(yīng)的錯誤原因 };
3.11 Makefile
.PHONY:all all:servercal clientcal Flag=#-DMySelf=1 Lib=-ljsoncpp servercal:ServerCal.cc g++ -o $@ $^ -std=c++11 $(Lib) $(Flag) clientcal:ClientCal.cc g++ -o $@ $^ -std=c++11 -g $(Lib) $(Flag) .PHONY:clean clean: rm -f clientcal servercal
四,再談七層協(xié)議
- 會話層: 由服務(wù)端解決獲取新鏈接,維護(hù)整個鏈接的使用情況,創(chuàng)建子進(jìn)程來對外提供服務(wù),相當(dāng)于沒訪問一次服務(wù)我就會創(chuàng)建一個新的會話,然后去處理新的連接,不需要就關(guān)掉
- 表示層:相當(dāng)于協(xié)議的序列化和反序列化
- 應(yīng)用層:處理數(shù)據(jù),不同的數(shù)據(jù)需要有不同的協(xié)議
為什么要壓成一層呢??因為以上都是在用戶層去實現(xiàn)的,因為方法由很多但是誰也說服不了誰,所以無法統(tǒng)一把他搞到OS模塊而必須由用戶根據(jù)不同的場景去定制
每次服務(wù)都要自己去定自定義協(xié)議嗎??其實是不需要的,有人想就會有人去做,所以應(yīng)用層的協(xié)議大部分不需要自己寫,可以直接用就可以了,因為他全都考慮到了。 一般公司產(chǎn)品比較嚴(yán)格不會讓你使用第三方庫。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Linux中的進(jìn)程守護(hù)supervisor安裝配置及使用
supervisor是一個很好的守護(hù)程序管理工具,配置方面自動啟動,日志輸出,自動切割日志等等一系列強大功能,下面是在CentOS下安裝使用supervisor的記錄,非常不錯,感興趣的朋友跟隨小編一起看看吧2019-07-07動態(tài)在線擴(kuò)容root根分區(qū)大小的方法詳解
這篇文章主要給大家介紹了關(guān)于如何動態(tài)在線擴(kuò)容root根分區(qū)大小的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05Yum中報錯:“pycurl.so: undefined symbol: CRYPTO_num_locks”的問題排查
這篇文章主要給大家介紹了在Yum中報錯: "pycurl.so: undefined symbol: CRYPTO_num_locks"的問題排查的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-06-06vscode遠(yuǎn)程開發(fā)使用SSH遠(yuǎn)程連接服務(wù)器的方法「內(nèi)網(wǎng)穿透」
這篇文章主要介紹了vscode遠(yuǎn)程開發(fā)使用SSH遠(yuǎn)程連接服務(wù)器?「內(nèi)網(wǎng)穿透」,通過本文學(xué)習(xí)我們將通過vscode實現(xiàn)遠(yuǎn)程開發(fā),并做內(nèi)網(wǎng)穿透實現(xiàn)在公網(wǎng)環(huán)境下的遠(yuǎn)程連接,在外任意地方也可以遠(yuǎn)程連接服務(wù)器進(jìn)行開發(fā)寫代碼,需要的朋友可以參考下2023-02-02