1. 问题引入
问题:TCP是面向字节流的(TCP不关心发送的数据是消息、文件还是其他任何类型的数据。它简单地将所有数据视为一个字节序列,即字节流。这意味着TCP不会对发送的数据进行任何特定的边界划分,它只是确保数据的顺序和完整性。),这怎么能保证,读上来的数据是一个 完整 的报文呢?
解答:通过协议(Protocol)来约定。协议定义了数据交换的规则和标准,使得不同设备之间能够相互通信和理解。
例如:
- 固定长度报文: 如果每个报文的长度都是固定的,那么接收方可以简单地读取固定数量的字节来构成一个完整的报文。
- 长度前缀: 在报文的开始处添加一个字段,指定了报文的长度。接收方首先读取长度字段,然后根据指定的长度读取后续的字节来构成完整的报文。
- 特殊分隔符: 使用一个特殊的字节序列或字符串作为报文的分隔符。接收方在流中搜索这个分隔符来确定报文的边界。例如,HTTP协议使用"\r\n\r\n"作为请求头和请求体的分隔符。
其它知识:传输层TCP是全双工的,意味着,TCP的收发是可以同时进行的。亦即接收的时候可以发送,发送的时候也可以接收,两者互不冲突,可同时进行。实际上客户端和服务端维护者两个缓冲区
2. 协议定制
2.1 序列化和反序列化的概念
序列化(Serialization)
序列化是指将对象的状态信息转换为可以存储或传输的格式(如JSON、XML、二进制等格式)的过程。序列化后的数据可以写入到文件中,或者通过网络发送到其他计算机。序列化的主要目的包括:
- 数据持久化:将内存中的数据结构保存到文件中,以便在程序下次运行时能够恢复。
- 网络传输:在网络上传输数据时,需要将复杂的数据结构转换为一种可以在网络上传输的格式。
- 跨语言和平台:不同的编程语言和平台之间交换数据时,需要一种中间格式来实现互操作性。
反序列化(Deserialization)
反序列化是序列化的逆过程,它是指将序列化后的数据(如文件中的数据或网络上接收到的数据)转换回原始的数据结构或对象状态的过程。反序列化使得程序能够从持久化的数据中恢复对象,或者接收并处理来自其他程序的数据。
序列化和反序列化的用途
- 数据库存储:将对象序列化后存储到数据库中,需要时再反序列化以恢复对象。
- 网络通信:在分布式系统或网络应用中,对象需要在网络上传输,因此需要序列化和反序列化。
- 缓存:将对象序列化后存储在缓存中,可以减少内存的使用。
- 消息队列:在使用消息队列(如RabbitMQ、Kafka)时,消息内容通常需要序列化。
2.2 网络版计算器 (服务端)
我们需要实现一个服务器版的加法器,在客户端把要计算的两个加数发过去, 然后由服务器进行计算,最后再把结果返回给客户端。
自己定制序列化规则:比如要计算a+b的结果,将其改为"len\n""a + b\n"
,第一个len
相当于报头,第二个字符串相当于报文的有效载荷
10+20 变为 "7\n""10 + 20\n"
2.2.1 自己定义协议
自定义协议 Protocol.hpp
#pragma once
#include <iostream>
#include <string>
using namespace std;
static const string BLANK_STRING = " ";
static const string PROTOCOL_SEP = "\n";
static string encode(const string& text)
{
// 添加报头
string ret = to_string(text.size());
ret += PROTOCOL_SEP;
ret += text;
ret += PROTOCOL_SEP;
return ret;
}
static bool decode(string& text, string* out)
{
// 移除报头 "len\n""a op b\n"???
size_t pos = text.find(PROTOCOL_SEP);
if(pos == string::npos) return false;
string headStr = text.substr(0, pos);
size_t textLen = stoi(headStr);
size_t totalLen = textLen + 2 + headStr.size();
// text可能除了"len\na op b\n"外增加了其它字符,比如"len\na op b\nlen\n..."。这里拿取了一个完整的
if(text.size() < totalLen) return false;
*out = text.substr(pos+1, textLen);
// 移除这一个完整的报文,防止text越来越大
text.erase(0, totalLen);
return true;
}
struct Request
{
int _a;
int _b;
char _op;
Request(int a, int b, char op) : _a(a), _b(b), _op(op) {}
Request() {}
bool serialize(string* out)
{
// 构建有效载荷,将成员属性变为 "_a op _b"
string tmp = to_string(_a);
tmp += BLANK_STRING;
tmp += _op;
tmp += BLANK_STRING;
tmp += to_string(_b);
*out = tmp;
return true;
}
bool deserialize(const string& in)
{
// 反序列化 将"_a op _b"拆分
size_t left = in.find(BLANK_STRING);
if(left == string::npos) {
cerr << "if(left == string::npos) err" << endl;
return false;
}
_a = stoi(in.substr(0, left));
size_t right = in.rfind(BLANK_STRING);
if(right == string::npos) {
cerr << "if(right == string::npos) err" << endl;
return false;
}
_b = stoi(in.substr(right+1));
if(left + 2 != right) {
cerr << "if(left + 2 != right) err" << endl;
return false;
}
_op = in[left+1];
return true;
}
void printInfo()
{
printf("%d %c %d = ?\n", _a, _op, _b);
}
};
struct Response
{
int _res;
int _exitCode; // 0 表示可信
Response(int res, int exitCode) : _res(res), _exitCode(exitCode) {}
Response() {}
bool serialize(string* out)
{
// 构建有效载荷,将成员属性变为 "_res op _exitCode"
string tmp = to_string(_res);
tmp += BLANK_STRING;
tmp += to_string(_exitCode);
*out = tmp;
return true;
}
bool deserialize(const string& in)
{
// 反序列化 将"_res op _exitCode"拆分
size_t left = in.find(BLANK_STRING);
if(left == string::npos) return false;
_res = stoi(in.substr(0, left));
_exitCode = stoi(in.substr(left+1));
}
void printInfo()
{
printf("res: %d, exitCode: %d\n", _res, _exitCode);
}
};
测试代码如下 SvrCal.cc
#include <iostream>
#include "TcpSvr.hpp"
#include "Protocol.hpp"
using namespace std;
void test1()
{
// 测试Request, 序列化 + 添加报头
Request r(122223, 456, '*');
string s;
r.serialize(&s);
s = encode(s);
cout << s;
// 去掉报头
string out;
decode(s, &out);
cout << out << endl;
// 反序列化
Request tmp;
tmp.deserialize(out);
printf("%d %c %d\n", tmp._a, tmp._op, tmp._b);
printf("===================================\n");
}
void test2()
{
// 测试Reponse, 序列化 + 添加报头
Response r(9999, 0);
string s;
r.serialize(&s);
s = encode(s);
cout << s;
// 去掉报头
string out;
decode(s, &out);
cout << out << endl;
// 反序列化
Response tmp;
tmp.deserialize(out);
printf("%d %d\n", tmp._res, tmp._exitCode);
printf("===================================\n");
}
int main()
{
test1();
test2();
return 0;
}
2.2.2 网络部分
Socket.hpp
,封装提供网络的系统调用接口
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <string>
#include <functional>
#include "log.hpp"
#define BACKLOG 10
Log log;
using namespace std;
class Sock
{
public:
Sock();
~Sock();
void Socket();
void Bind(uint16_t port);
void Listen();
int Accept(string* ip, uint16_t* port);
bool Connect(const string& ip, const uint16_t& port);
int GetFd();
void Close();
private:
int _socketFd;
};
Sock::Sock() : _socketFd(-1)
{}
Sock::~Sock()
{}
inline void Sock::Socket()
{
_socketFd = socket(AF_INET, SOCK_STREAM, 0);
if(_socketFd < 0) {
log(FATAL, "Sock::Socket() error! why: %s\n", strerror(errno));
exit(-1);
}
}
inline void Sock::Bind(uint16_t port)
{
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(_socketFd, (sockaddr*)&local, sizeof local) < 0) {
log(FATAL, "Sock::Bind() error! why: %s\n", strerror(errno));
exit(-1);
}
}
inline void Sock::Listen()
{
if(listen(_socketFd, BACKLOG) < 0) {
log(FATAL, "Sock::Listen() error!\n");
exit(-1);
}
}
inline int Sock::Accept(string* peerIp, uint16_t* peerPort)
{
sockaddr_in peer;
memset(&peer, 0, sizeof peer);
socklen_t len = sizeof len;
int socketFd = accept(_socketFd, (sockaddr*)&peer, &len);
if(socketFd < 0) {
log(WARNING, "Sock::Accept() error!\n");
return -1;
}
// 将客户端的ip输出出去
char buf[64];
if(inet_ntop(AF_INET, &peer.sin_addr, buf, sizeof buf) == nullptr) {
log(WARNING, "Sock::Accept() error!\n");
return -1;
}
*peerIp = buf;
// 将客户端的端口号输出出去
*peerPort = ntohs(peer.sin_port);
return socketFd;
}
inline bool Sock::Connect(const string& ip, const uint16_t& port)
{
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &server.sin_addr.s_addr);
int ret = connect(_socketFd, (sockaddr*)&server, sizeof server);
if(ret < 0) {
cerr << "Sock::Connect error!" << endl;
return false;
}
return true;
}
inline int Sock::GetFd()
{
return _socketFd;
}
inline void Sock::Close()
{
close(_socketFd);
}
2.2.3 处理数据
SvrCal.hpp
,处理来自服务器的数据,doCalculate(string &text)
中的text
需要满足自定义协议要求的字符串
#pragma once
#include "Protocol.hpp"
enum {
Div_Zero = 1,
Mod_Zero,
Other_Err,
};
class SvrCal
{
public:
SvrCal() {}
~SvrCal() {}
// 计算text中的数据,出错返回空字符串
string doCalculate(string& text)
{
// 将从网络上来的数据,去掉报头
string out;
bool r = decode(text, &out);
if(r == false) {
// cerr << "decode() error" << endl;
return "";
}
// printf("Now out: %s\n", out.c_str());
// 反序列化
Request req;
r = req.deserialize(out);
if(r == false) {
cerr << "deserialize() error" << endl;
return "";
}
// 计算结果
Response resp = calHelper(req);
// 序列化
out = "";
resp.serialize(&out);
// 将计算结果加上报头
out = encode(out);
return out;
}
private:
Response calHelper(Request& req)
{
// 将Request转换为Response
Response resp;
switch (req._op)
{
case '+':
resp._res = req._a + req._b;
break;
case '-':
resp._res = req._a - req._b;
break;
case '*':
resp._res = req._a * req._b;
break;
case '/':
{
if(req._b == 0)
resp._exitCode = Div_Zero;
else
resp._res = req._a / req._b;
}
break;
case '%':
{
if(req._b == 0)
resp._exitCode = Mod_Zero;
else
resp._res = req._a % req._b;
}
break;
default:
resp._exitCode = Other_Err;
break;
}
return resp;
}
};
2.2.4 服务器代码
TcpSvr.hpp
,_callback
后面会绑定到SvrCal::doCalculate(string& text)
#pragma once
#include "Socket.hpp"
#include <signal.h>
extern Log log;
/*
目前使用的是SvrCal.hpp中的
string doCalculate(const string& text)
*/
using fun_t = function<string(string&)>;
class TcpSvr
{
public:
TcpSvr();
TcpSvr(uint16_t port, fun_t callback) : _port(port), _callBack(callback) {}
bool initSvr();
void startSvr();
private:
uint16_t _port;
Sock _listSock;
fun_t _callBack;
};
inline bool TcpSvr::initSvr()
{
_listSock.Socket();
_listSock.Bind(_port);
_listSock.Listen();
log(INFO, "TcpSvr Init over.\n");
}
inline void TcpSvr::startSvr()
{
signal(SIGCHLD, SIG_IGN);
for(;;) {
string peerIp;
uint16_t peerPort;
int socketFd = _listSock.Accept(&peerIp, &peerPort);
if(socketFd < 0) {
sleep(1);
continue;
}
log(INFO, "TcpSvr is Accepting, client IP: %s, clientPort %d\n", peerIp.c_str(), peerPort);
pid_t id = fork();
if(id < 0) {
log(WARNING, "TcpSvr::startSvr() fork error!\n");
sleep(1);
continue;
} else if(id == 0) {
// 关闭_listenFd的目的是为了防止子进程修改父进程文件描述符下的内容
_listSock.Close();
string inStr="";
for(;;) {
char buf[128];
ssize_t n = read(socketFd, buf, sizeof buf);
if (n < 0) {
log(WARNING, "TcpSvr::startSvr() read error!\n");
// sleep(1);
break;
} else if(n == 0) {
log(WARNING, "TcpSvr::startSvr() read over!\n");
// sleep(1);
break;
} else {
buf[n] = 0;
inStr += buf;
log(DEBUG, "Read from clinet: \n%s", inStr.c_str());
fflush(stdout);
// 回调函数,计算结果
string r = _callBack(inStr);
if(r == "") {
// 报文不正确,重新读取
// log(WARNING, "TcpSvr::startSvr() doCalculate error!\n");
continue;
// sleep(1);
}
write(socketFd, r.c_str(), r.size());
}
}
exit(0);
} else {
// 父进程关闭socketFd的意义是为了防止文件描述符越用越少,关闭了之后能保证每一个新的进程用的都是fd都是4
close(socketFd);
}
}
}
SvrCal.cc
,编译的是该文件
#include <iostream>
#include <functional>
#include "TcpSvr.hpp"
#include "Protocol.hpp"
#include "SvrCal.hpp"
using namespace std;
// test1() 和 test()和2.2.1中测试代码部分一样
void test1()
{}
void test2()
{}
int main(int argc, char* argv[])
{
if(argc != 2) {
cout << "Usage error!\n";
return -1;
}
uint16_t port = stoi(argv[1]);
SvrCal cal;
TcpSvr *svr = new TcpSvr(port, bind(&SvrCal::doCalculate, &cal, placeholders::_1));
// printf("after new TcpSvr()---\n");
svr->initSvr();
// printf("after new initSvr()---\n");
svr->startSvr();
return 0;
}
3\n6 0
是Response
序列化加上报头结果
2.3 网络计算器 (客户端)
2.3.1 随机生成数字
客户端也要遵循协议
CliCal.cc
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include "Socket.hpp"
#include "Protocol.hpp"
using namespace std;
// ./myClient ip port
int main(int argc, char* argv[])
{
if(argc != 3) {
cerr << "Usage error" << endl;
return -1;
}
string serverIp = argv[1];
uint16_t serverPort = stoi(argv[2]);
Sock sock;
sock.Socket();
bool ret = sock.Connect(serverIp, serverPort);
if(ret == false) {
return -1;
}
srand(time(0));
const string ops = "+-*/&?="; // 有一些不正确的符号,目的是为了测试出错的情况
string inStr = "";
for (int i = 1; i <= 10; ++i) {
printf("==============第%d次================\n", i);
int x = rand() % 100;
usleep(100);
int y = rand() % 100;
usleep(100);
char op = ops[rand() % ops.size()];
string text;
Request req(x, y, op);
req.printInfo();
req.serialize(&text);
text = encode(text);
printf("将要发送给服务器的请求:\n%s", text.c_str());
// 向服务器发送数据
ssize_t n = write(sock.GetFd(), text.c_str(), text.size());
// n = write(sock.GetFd(), text.c_str(), text.size());
// n = write(sock.GetFd(), text.c_str(), text.size());
// n = write(sock.GetFd(), text.c_str(), text.size());
if(n < 0) {
cerr << "Client write error!\n" << endl;
break;
}
// 从服务器读取数据
char buf[128];
memset(buf, 0, sizeof buf);
n = read(sock.GetFd(), buf, sizeof(buf));
if(n > 0) {
buf[n] = 0;
inStr += buf;
string text;
bool r = decode(inStr, &text);
if(r == false) {
cerr << "Client decode error!\n" << endl;
break;
}
Response resp;
resp.deserialize(text);
printf("从服务器得到结果:\n");
resp.printInfo();
}
printf("======================================\n");
sleep(1);
}
sock.Close();
return 0;
}
由于可能有多个客户端向服务器发送请求,所以TcpSvr.hpp
中的startSvr()
改为如下的格式
inline void TcpSvr::startSvr()
{
signal(SIGCHLD, SIG_IGN);
for(;;) {
string peerIp;
uint16_t peerPort;
int socketFd = _listSock.Accept(&peerIp, &peerPort);
if(socketFd < 0) {
sleep(1);
continue;
}
log(INFO, "TcpSvr is Accepting, client IP: %s, clientPort %d\n", peerIp.c_str(), peerPort);
pid_t id = fork();
if(id < 0) {
log(WARNING, "TcpSvr::startSvr() fork error!\n");
sleep(1);
continue;
} else if(id == 0) {
// 关闭_listenFd的目的是为了防止子进程修改父进程文件描述符下的内容
_listSock.Close();
string inStr="";
for(;;) {
char buf[128];
ssize_t n = read(socketFd, buf, sizeof buf);
if (n < 0) {
log(WARNING, "TcpSvr::startSvr() read error!\n");
// sleep(1);
break;
} else if(n == 0) {
log(WARNING, "TcpSvr::startSvr() read over!\n");
// sleep(1);
break;
} else {
buf[n] = 0;
inStr += buf;
log(DEBUG, "Read from clinet: \n%s", inStr.c_str());
fflush(stdout);
while (true) {
// 客户端可能发送多次数据,所以这里一次全部处理干净
// 回调函数,计算结果
string r = _callBack(inStr);
if(r == "") {
break;
}
write(socketFd, r.c_str(), r.size());
}
}
}
exit(0);
} else {
// 父进程关闭socketFd的意义是为了防止文件描述符越用越少,关闭了之后能保证每一个新的进程用的都是fd都是4
close(socketFd);
}
}
}
现在的运行结果如下图
2.4 使用json
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它基于JavaScript的一个子集,但是独立于语言,可以被多种编程语言读取。JSON采用文本格式,易于阅读和编写,同时也易于机器解析和生成。
JSON的结构包括:
- 键值对:JSON中的每个元素都由键值对组成,键和值之间用冒号(:)分隔,键名必须用双引号括起来。
- 数据类型:JSON支持的数据类型包括字符串(String)、数字(Number)、对象(Object)、数组(Array)、布尔值(Boolean)和空值(null)。
- 数组:数组在JSON中用方括号([])表示,数组中的元素可以是任何类型的值。
- 对象:对象在JSON中用花括号({})表示,对象中的元素是键值对。
一个简单的JSON示例如下:
{
"name": "John",
"age": 30,
"is_student": false,
"courses": ["Math", "Science", "History"],
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA"
}
}
在这个例子中,name
、age
、is_student
是键值对,courses
是一个数组,address
是一个对象。JSON格式的数据可以被JavaScript直接解析,也可以被其他支持JSON的编程语言解析和生成。
2.4.1 安装json
sudo yum install -y jsoncpp-devel
安装完成后,json库用到的头文件
json库的位置
2.4.2 简单使用
序列化,写一个main.cc
用于测试
#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
using namespace std;
int main()
{
// 创建一个 Json::Value 类型的对象 root,它将作为 JSON 对象的根。
Json::Value root;
// 构建键值对
root["x"] = 1;
root["y"] = 2;
root["op"] = "+";
root["desc"] = "add operation";
// 创建一个 Json::FastWriter 对象 w,用于将 JSON 对象转换为字符串。
Json::FastWriter w;
// 使用 w.write(root) 将 root JSON 对象转换为字符串,并存储在变量 res 中。
string res = w.write(root);
cout << res << endl;
return 0;
}
除了使用Json::FastWriter
,也可以使用Json::StyledWriter
,可读性会好一点
// 将上面代码的第16行改为:
Json::StyledWriter w;
继续写上面的代码,下面进行反序列化
int main()
{
// 创建一个 Json::Value 类型的对象 root,它将作为 JSON 对象的根。
Json::Value root;
root["x"] = 1;
root["y"] = 2;
root["op"] = "+";
root["desc"] = "add operation";
// 创建一个 Json::FastWriter 对象 w,用于将 JSON 对象转换为字符串。
// Json::FastWriter w;
Json::StyledWriter w;
// 使用 w.write(root) 将 root JSON 对象转换为字符串,并存储在变量 res 中。
string res = w.write(root);
cout << res << endl;
// 下面是反序列化
Json::Value v; // 用来存储解析后的 JSON 数据。
Json::Reader r; // 用来解析 JSON 字符串。
r.parse(res, v); // 将 JSON 字符串 res 解析到 v 对象中。
int x = v["x"].asInt();
int y = v["y"].asInt();
string op = v["op"].asString();
string desc = v["desc"].asString();
cout << x << endl;
cout << y << endl;
cout << op << endl;
cout << desc << endl;
return 0;
}
Json里面也可以再套一个Json
#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
using namespace std;
int main()
{
// 序列化
Json::Value inner;
inner["hello"] = "你好";
inner["world"] = "世界";
Json::Value root;
root["test"] = inner;
Json::StyledWriter w;
string res = w.write(root);
cout << res;
// 反序列化
Json:: Value v;
Json:: Reader r;
r.parse(res, v);
cout << v["test"]["hello"].asString() << endl;
cout << v["test"]["world"].asString() << endl;
return 0;
}
2.4.3 修改协议部分
给2.2.1中的Protocol.hpp
添加条件编译,使用jsoncpp
这个库
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
using namespace std;
static const string BLANK_STRING = " ";
static const string PROTOCOL_SEP = "\n";
static string encode(const string& text)
{
// 添加报头
string ret = to_string(text.size());
ret += PROTOCOL_SEP;
ret += text;
ret += PROTOCOL_SEP;
return ret;
}
static bool decode(string& text, string* out)
{
// 移除报头 "len\n""a op b\n"???
size_t pos = text.find(PROTOCOL_SEP);
if(pos == string::npos) return false;
string headStr = text.substr(0, pos);
size_t textLen = stoi(headStr);
size_t totalLen = textLen + 2 + headStr.size();
// text可能除了"len\na op b\n"外增加了其它字符,比如"len\na op b\nlen\n..."。这里拿取了一个完整的
if(text.size() < totalLen) return false;
*out = text.substr(pos+1, textLen);
// 移除这一个完整的报文,防止text越来越大
text.erase(0, totalLen);
return true;
}
struct Request
{
int _a;
int _b;
char _op;
Request(int a, int b, char op) : _a(a), _b(b), _op(op) {}
Request() {}
bool serialize(string* out)
{
#ifdef MYSELF
// 构建有效载荷,将成员属性变为 "_a op _b"
string tmp = to_string(_a);
tmp += BLANK_STRING;
tmp += _op;
tmp += BLANK_STRING;
tmp += to_string(_b);
*out = tmp;
return true;
#else
Json::Value tmp;
tmp["x"] = _a;
tmp["op"] = _op;
tmp["y"] = _b;
Json::FastWriter w;
*out = w.write(tmp);
return true;
#endif
}
bool deserialize(const string& in)
{
#ifdef MYSELF
// 反序列化 将"_a op _b"拆分
size_t left = in.find(BLANK_STRING);
if(left == string::npos) {
cerr << "if(left == string::npos) err" << endl;
return false;
}
_a = stoi(in.substr(0, left));
size_t right = in.rfind(BLANK_STRING);
if(right == string::npos) {
cerr << "if(right == string::npos) err" << endl;
return false;
}
_b = stoi(in.substr(right+1));
if(left + 2 != right) {
cerr << "if(left + 2 != right) err" << endl;
return false;
}
_op = in[left+1];
return true;
#else
Json::Value v;
Json::Reader r;
r.parse(in, v);
_a = v["x"].asInt();
_op = v["op"].asInt();
_b = v["y"].asInt();
return true;
#endif
}
void printInfo()
{
printf("%d %c %d = ?\n", _a, _op, _b);
}
};
struct Response
{
int _res;
int _exitCode; // 0 表示可信
Response(int res, int exitCode) : _res(res), _exitCode(exitCode) {}
Response() {}
bool serialize(string* out)
{
#ifdef MYSELF
// 构建有效载荷,将成员属性变为 "_res op _exitCode"
string tmp = to_string(_res);
tmp += BLANK_STRING;
tmp += to_string(_exitCode);
*out = tmp;
return true;
#else
Json::Value tmp;
tmp["res"] = _res;
tmp["code"] = _exitCode;
Json::FastWriter w;
*out = w.write(tmp);
return true;
#endif
}
bool deserialize(const string& in)
{
#ifdef MYSELF
// 反序列化 将"_res op _exitCode"拆分
size_t left = in.find(BLANK_STRING);
if(left == string::npos) return false;
_res = stoi(in.substr(0, left));
_exitCode = stoi(in.substr(left+1));
#else
Json::Value v;
Json::Reader r;
r.parse(in, v);
_res = v["res"].asInt();
_exitCode = v["code"].asInt();
return true;
#endif
}
void printInfo()
{
printf("res: %d, exitCode: %d\n", _res, _exitCode);
}
};
Makefile
格式如下,默认将flag
注释,这样就没有定义MYSELF
.PHONY : all
all : myClient myServer
lib = -ljsoncpp
#flag = -DMYSELF=1 # 是否有该宏
myClient : CliCal.cc
g++ -o $@ $^ -std=c++11 $(lib) $(flag)
myServer : SvrCal.cc
g++ -o $@ $^ -std=c++11 $(lib) $(flag)
.PHONY : clean
clean:
rm -rf myClient myServer
运行结果如下
2.4.4 守护进场话
可以使用链接中的3.3.2deamon.hpp
也可以调用下面的系统调用
#include <unistd.h>
int daemon(int nochdir, int noclose);
参数说明:
nochdir
:如果设置为非零值,daemon
函数不会改变当前工作目录到根目录(/
)。默认情况下,daemon
函数会将当前工作目录改变到根目录。noclose
:如果设置为非零值,daemon
函数不会关闭所有文件描述符。默认情况下,daemon
函数会关闭所有文件描述符。
返回值:
- 如果成功,返回 0。
- 如果失败,返回 -1,并设置
errno
以指示错误。
修改SvrCal.c
,加上daemon()
#include <iostream>
#include <functional>
#include <unistd.h>
#include "TcpSvr.hpp"
#include "Protocol.hpp"
#include "SvrCal.hpp"
using namespace std;
int main(int argc, char* argv[])
{
if(argc != 2) {
cout << "Usage error!\n";
return -1;
}
uint16_t port = stoi(argv[1]);
SvrCal cal;
TcpSvr *svr = new TcpSvr(port, bind(&SvrCal::doCalculate, &cal, placeholders::_1));
svr->initSvr();
int r = daemon(0, 0);
if(r < 0) {
cout << "Daemon error!\n";
return -2;
}
svr->startSvr();
return 0;
}
可以看到,守护进程已经被正确初始化了