Linux中的自定義協(xié)議+序列反序列化用法
我們程序員寫的一個個解決我們實際問題, 滿足我們?nèi)粘P枨蟮木W(wǎ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ā)送過程中的可靠性由你來維護。(就像當年我們只需要將內(nèi)容寫到文件內(nèi)核緩沖區(qū),而由OS來決定什么時候,以什么方式刷新到磁盤上)
(2)所以TCP協(xié)議是屬于操作系統(tǒng)的網(wǎng)絡模塊部分,他之所以叫做傳輸控制協(xié)議,就是因為他需要確保數(shù)據(jù)傳輸過程中的可靠性,比方說我什么時候應該發(fā)給對方?要發(fā)多少?萬一出錯了怎么辦?
思考2:回憶管道和文件系統(tǒng)
(1)以往我們往管道文件里寫了很多次的時候,可能我們一次就全部讀上來了
(2)而在文件系統(tǒng)中,我們的寫很容易,可以分很多次寫,各種類型比如整數(shù)/字符串…… 但是讀的時候就很難去讀,同時不同的文件的讀取方式可能也不一樣,比如按行讀也僅僅只是讀取文件的一種方式而已。
思考3:TCP是面向字節(jié)流的,你怎么保證你讀上來的數(shù)據(jù)一定是一個"完整的報文"呢?
(1)早期的時候我們可能會想到比方說我們約定必須要湊齊多少字節(jié)才往上讀,但是這個其實只適用于一些固定類型的報文,比方說我們規(guī)定讀的是int,而int對應的就是一個錯誤碼或者是某一個固定的任務。但是如果是一些長度不固定的報文,比如說我們在聊天的時候發(fā)送的字符串長度都是不一樣的,那么這個時候就很容易讀到不完整的報文
(2)所以為了應對這種情況,我們就需要在報文直接加入一下分割符,或者是標識這個報文有多長,以確保能夠讀到完整的報文, 而協(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)绻米址兀颗e個例子,我們聊天的時候我發(fā)了一句哈哈,但是發(fā)送的時候還會帶上昵稱以及發(fā)送時間,所以我們肯定不能把這三個字符串分開發(fā),因為這也服務端就不知道你這話是誰說的,所以我肯定希望把三個字符串打包成一個字符串發(fā)過去,然后服務的把消息廣播給所有客戶端的時候,會再把這個包根據(jù)一定的方法解析分成三個字符串然后再給你顯現(xiàn)出來!------------->也就是說我們需要在類里面定義兩種方法,一種是把類內(nèi)的數(shù)據(jù)打包成一個字符串發(fā)送過去,另一種是解析的時候?qū)⑦@個字符串再拆分出來。 而這個過程就是序列化和反序列化
問題2 :我們需要實現(xiàn)一個服務器版的加法器. 我們需要客戶端把要計算的兩個加數(shù)發(fā)過去, 然后由服務器進行計算, 最 后再把結(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)絡計算器
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);
// // 格式:默認部分+自定義部分
// 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);
// 格式:默認部分+自定義部分
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)聽隊列默認長度
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)絡字節(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); // 看看服務器在哪個端口上運行
}
void Start() // 啟動服務器
{
_isrunning = true;
signal(SIGCHLD, SIG_IGN); // 忽略子進程結(jié)束信號 因為子進程結(jié)束時會產(chǎn)生SIGCHLD信號
signal(SIGPIPE, SIG_IGN); // 忽略管道錯誤信號 因為當網(wǎng)絡連接中某個套接字被關(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)建子進程 幫助我們處理每個客戶端的請求
if (pid < 0) // 出錯
{
lg(Error, "Fork error");
continue;
}
if (pid == 0) // 子進程
{
_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ù)獲取服務端需要的數(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); // 子進程結(jié)束
}
close(sockfd); // 關(guān)閉套接字
}
}
private:
uint16_t _port; // 端口號
Sock _listensock; // 監(jiān)聽套接字
func_t _callback; // 回調(diào)函數(shù) 用來提供服務
// ip模式是0
bool _isrunning; // 是否運行
};
1、tcpserver只負責網(wǎng)絡通信讀到了那些數(shù)據(jù),但是關(guān)于數(shù)據(jù)的解析 全部交給callback回調(diào)函數(shù)去處理這樣就是將網(wǎng)絡通信和協(xié)議解析進行了解耦
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é)果響應完成, result: " << _result << ", code: "<< _code << std::endl;
}
public:
int _result;
int _code; // 0,可信,否則!0具體是幾,表明對應的錯誤原因
};不僅需要有序列化和反序列化的方法,還要有添加報頭和解析報頭(要有很多檢查 將一個有效的報文提取出來)的方法
3.5 Servercal.hpp
// 計算器服務
#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;
}
//設計一個回調(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);//嘗試和服務端連接
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. 更改當前調(diào)用進程的工作目錄
if (!cwd.empty())
chdir(cwd.c_str());
// 4. 標準輸入,標準輸出,標準錯誤重定向至/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是風格寫
(4)asInt表示解析成int類型
3.10Protocol.hpp改進版
#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é)果響應完成, result: " << _result << ", code: "<< _code << std::endl;
}
public:
int _result;
int _code; // 0,可信,否則!0具體是幾,表明對應的錯誤原因
};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é)議

- 會話層: 由服務端解決獲取新鏈接,維護整個鏈接的使用情況,創(chuàng)建子進程來對外提供服務,相當于沒訪問一次服務我就會創(chuàng)建一個新的會話,然后去處理新的連接,不需要就關(guān)掉
- 表示層:相當于協(xié)議的序列化和反序列化
- 應用層:處理數(shù)據(jù),不同的數(shù)據(jù)需要有不同的協(xié)議
為什么要壓成一層呢??因為以上都是在用戶層去實現(xiàn)的,因為方法由很多但是誰也說服不了誰,所以無法統(tǒng)一把他搞到OS模塊而必須由用戶根據(jù)不同的場景去定制
每次服務都要自己去定自定義協(xié)議嗎??其實是不需要的,有人想就會有人去做,所以應用層的協(xié)議大部分不需要自己寫,可以直接用就可以了,因為他全都考慮到了。 一般公司產(chǎn)品比較嚴格不會讓你使用第三方庫。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Yum中報錯:“pycurl.so: undefined symbol: CRYPTO_num_locks”的問題排查
這篇文章主要給大家介紹了在Yum中報錯: "pycurl.so: undefined symbol: CRYPTO_num_locks"的問題排查的相關(guān)資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。2017-06-06
vscode遠程開發(fā)使用SSH遠程連接服務器的方法「內(nèi)網(wǎng)穿透」
這篇文章主要介紹了vscode遠程開發(fā)使用SSH遠程連接服務器?「內(nèi)網(wǎng)穿透」,通過本文學習我們將通過vscode實現(xiàn)遠程開發(fā),并做內(nèi)網(wǎng)穿透實現(xiàn)在公網(wǎng)環(huán)境下的遠程連接,在外任意地方也可以遠程連接服務器進行開發(fā)寫代碼,需要的朋友可以參考下2023-02-02

