自定义协议
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转换为ResponseResponse 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都是4close(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都是4close(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));
#elseJson::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 myServerlib = -ljsoncpp
#flag = -DMYSELF=1 # 是否有该宏myClient : CliCal.ccg++ -o $@ $^ -std=c++11 $(lib) $(flag)
myServer : SvrCal.ccg++ -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;
}
可以看到,守护进程已经被正确初始化了
相关文章:
自定义协议
1. 问题引入 问题:TCP是面向字节流的(TCP不关心发送的数据是消息、文件还是其他任何类型的数据。它简单地将所有数据视为一个字节序列,即字节流。这意味着TCP不会对发送的数据进行任何特定的边界划分,它只是确保数据的顺序和完整…...
PHP Date 函数:日期和时间处理的全指南
PHP Date 函数:日期和时间处理的全指南 PHP Date 函数是 PHP 编程语言中用于处理日期和时间的核心函数之一。它提供了强大的功能,允许开发者轻松地格式化、计算和操作日期和时间值。本文将详细介绍 PHP Date 函数的用法,包括基本格式化、时间戳处理、时区设置以及一些高级特…...
C++设计模式:抽象工厂模式(风格切换案例)
抽象工厂模式(Abstract Factory)是一种创建型设计模式,其核心思想是:为一组相关或相互依赖的对象提供一个创建接口,而无需指定它们具体的类。简单来说,就是一个工厂可以生产一系列相关的对象。 我们接下来…...
社交媒体营销新趋势:如何通过海外平台提升品牌曝光度?
社交媒体不仅是简单的信息传播工具,更是连接用户与品牌之间的重要纽带。每天,有数以亿计的全球用户在不同平台上活跃,潜藏着巨大的市场潜力。对于企业来说,关键在于制定清晰的营销策略,精准把握不同社交平台的特性&…...
嵌入式C/C++编译常见问题与分析
#1 kcx.c:112:89: error: format specifies type unsigned int but the argument has type u32 * (aka unsigned int *) [-Werror,-Wformat] 报错原因: int kcx(u32 *trigger) { ERR_MSG("%s:failed. attr%d, trigger%u\n", __func__, attr_enable, trig…...
laravel 5.5 增加宏指令 joinSub, 省去->toSql() 和 addBinding($bindings);
laravel 5.5 增加宏指令 joinSub, 省去->toSql() 和 addBinding($bindings); 1. 在laravel5使用join 子查询时 $sub_query DB::table(table1)->select([table1.id, cate_id])->join(table2, table1.id, , table2.id)->where(table1.cate_id, 2)->orderBy(tabl…...
知识库搭建:大健康产业数字化转型的新引擎
随着数字经济的蓬勃发展,大健康产业正步入一个崭新的发展篇章。消费者对于健康的追求日益增长,促使大健康企业积极探索数字化路径,以提升供应链效率、控制成本,并在激烈的市场竞争中脱颖而出。在此过程中,一系列数字化…...
创建可重用React组件的实用指南
尽管React是全球最受欢迎和使用最广泛的前端框架之一,但许多开发者在重构代码以提高可复用性时仍然感到困难。如果你发现自己在React应用中不断重复相同的代码片段,那你来对地方了。 在本教程中,将向你介绍三个最常见的特征,表明是…...
蓝桥杯每日真题 - 第18天
题目:(出差) 题目描述(13届 C&C B组E题) 解题思路: 问题分析 问题实质是一个带权图的最短路径问题,但路径的权重包含两个部分: 从当前城市到下一个城市的路程时间。 当前城市的…...
MySQL中索引全详解
第一部分:什么是索引 索引在数据库中就像书的目录,能够快速定位数据位置,从而提升查询效率。没有索引时,数据库查询需要从头到尾扫描整个表(称为全表扫描),这在数据量大时非常耗时。有了索引后&…...
探索复合物TPP-PEG-Heparin的特性;磷酸三苯酯-聚乙二醇-肝素的线粒体靶向性
TPP-PEG-Heparin,即磷酸三苯酯(TPP)、聚乙二醇(PEG)和肝素(Heparin)的复合物,其特性融合了这三种成分的性质。 一、线粒体靶向性 TPP部分:具有线粒体靶向功能…...
ubuntu 配置 多个 git 客户端 账户
Git配置两个或多个账户 https://blog.csdn.net/mainking2003/article/details/134711865 git 提交 不用输入用户名、密码的方法(GIT免密提交) https://blog.csdn.net/wowocpp/article/details/125797263 git config 用法 https://blog.csdn.net/blueb…...
Web3与智能合约:区块链技术下的数字信任体系
随着互联网的不断发展,Web3代表着我们迈入了一个去中心化、更加安全和智能的网络时代。作为Web3的核心组成部分,区块链技术为智能合约的出现和发展提供了强有力的基础。智能合约不仅仅是自动化的代码,它们正逐步成为重塑数字世界信任体系的关…...
RocketMQ文件刷盘机制深度解析与Java模拟实现
引言 在现代分布式系统中,消息队列(Message Queue, MQ)作为一种重要的中间件,扮演着连接不同服务、实现异步通信和消息解耦的关键角色。Apache RocketMQ作为一款高性能的分布式消息中间件,广泛应用于实时数据流处理、…...
高级编程之结构化代码
背景:以下没结构化代码之前,定时器同步店铺信息的代码。 结构化的思想:SRP(单一职责),一个方法是做一件事,层次是相关的,逻辑和数据操作进行拆分,数据操作从业务流程上定…...
学习编程,学习中间件,学习源码的思路
01 看的多,内化不足 最近想复习一下编程相关的知识,在复习前我翻开了之前的一些笔记,这些笔记基本都是从书本、视频、博客等摘取记录的,看着这些笔记心里总结:看的多,内化不足。 02 整理大纲 为了解决这个…...
网络安全与加密
1.Base64简单说明描述:Base64可以成为密码学的基石,非常重要。特点:可以将任意的二进制数据进行Base64编码结果:所有的数据都能被编码为并只用65个字符就能表示的文本文件。65字符:A~Z a~z 0~9 / 对文件进行base64编码…...
开源协议介绍
文章目录 1. MIT License2. Apache License 2.03. GNU General Public License (GPL)4. GNU Lesser General Public License (LGPL)5. BSD License6. Mozilla Public License (MPL)7. Creative Commons Licenses (CC)8. Unlicense选择建议 在 开源平台上,开源项目通…...
Java技术复习提升 10异常
10 异常 10.1异常介绍及分类 异常捕获 选中后alttabt->选中try-catch 异常就是程序执行中不正常的情况 注意语法和逻辑错误并不是异常 异常分类有两种 error和exception error是错误 虚拟机无法解决的严重问题 exception是其他因为编程错误或者外在因素导致的一般性的问…...
java版工程项目管理系统源码:Spring Cloud与前后端分离的完美结合
在现代化的工程项目管理中,一套功能全面、操作便捷的系统至关重要。本文将介绍一个基于Spring Cloud和Spring Boot技术的Java版工程项目管理系统,结合Vue和ElementUI实现前后端分离。该系统涵盖了项目管理、合同管理、预警管理、竣工管理、质量管理等多个…...
Oracle与MySQL中CONCAT()函数的使用差异
一、CONCAT函数介绍 CONCAT函数是MySQL等数据库中用于连接两个或多个字符串的内置函数。其基本语法如下: CONCAT(string1, string2, ...)参数说明: string1, string2, …:需要连接的字符串参数,可以有多个。 返回值࿱…...
AI社媒引流工具:解锁智能化营销的新未来
在数字化浪潮的推动下,社交媒体成为品牌营销的主战场。然而,面对海量的用户数据和日益复杂的运营需求,传统营销方法显得力不从心。AI社媒引流王应运而生,帮助企业在多平台中精准触达目标用户,提升营销效率和效果。 1.…...
浏览器的事件循环机制
一、请简述浏览器的事件循环机制(Event Loop)基本原理 浏览器的事件循环机制是用于协调处理 JavaScript 中的异步任务与同步任务执行顺序的一种机制,它确保了代码能够按照合理的顺序执行,避免阻塞页面渲染等情况。其基本原理如下…...
如何在 React 项目中应用 TypeScript?应该注意那些点?结合实际项目示例及代码进行讲解!
在 React 项目中应用 TypeScript 是提升开发效率、增强代码可维护性和可读性的好方法。TypeScript 提供了静态类型检查、自动补全和代码提示等功能,这对于 React 开发者来说,能够帮助早期发现潜在的 bug,提高开发体验。 1. 项目初始化 在现…...
排序算法(五)--归并排序
文章目录 引言归并排序概述C语言实现代码解析结论 归并排序 C语言实例 引言 归并排序(Merge Sort)作为一种经典的排序算法,以其稳定性、分治法的巧妙应用以及相对高效的时间复杂度而著称。 归并排序概述 归并排序采用分治法(Di…...
Linux KASLR 地址偏移
kaslr开启时地址 cat /proc/cmdline BOOT_IMAGE/boot/vmlinuz-5.4.0-193-generic rootUUID0e46dee3-4557-434a-a2d2-a35c6ad3d327 ro find_preseed/preseed.cfg auto noprompt prioritycritical localeen_US quiet cat /boot/config-$(uname -r) | grep CONFIG_RANDOMIZE_B…...
利用开源图床的技巧与实践
随着互联网的普及,图片的使用变得越来越广泛。无论是个人博客、社交媒体还是企业网站,都离不开图片的呈现。而图床作为图片存储和管理的工具,可以帮助开发者和内容创作者高效地管理图片资源。本文将探讨如何利用开源图床,并提供相…...
Unity Lua方向的面试真题详解
最近有位同学面试Unity,面试的公司采用Lua的方案来做公司项目,我们把面试时问道的真题列举出来,并配上参考回复。 1、Lua热更文件时,文件是重写的,还是只写一部分? 热更分为资源更新和代码更新,资源更新…...
经验笔记:Git 基础操作指南
推荐一下Gitee最好的Git操作教程:Learn Git Branching 经验笔记:Git 基础操作指南 1. 安装 Git 首先确保您的计算机上已安装 Git。如果还没有安装,可以从 Git官网 下载并安装。 2. 配置 Git 安装完成后,打开命令行工具&#…...
大模型在智能客服中心领域的应用思考
大模型在智能客服中心领域的应用思考 作者:开源呼叫中心系统 FreeIPCC,Github地址:https://github.com/lihaiya/freeipcc 随着人工智能技术的飞速发展,特别是深度学习技术的突破,大型语言模型(LLMs&#x…...
ssm旅游推荐系统的设计与开发
摘 要 旅游推荐系统是一个综合性的在线旅游推荐平台,旨在为用户提供便捷的旅游规划和预定服务。通过该系统,用户能够浏览各类景点信息并进行分类查找,同时获取详尽的景点介绍和相关照片,以辅助做出旅行决策。系统提供在线门票订购…...
C++从零到满绩——入门基础and类和对象(上)
目录 1>>前言 2>>函数重载 3>>引用 3.1>>引用的概念 3.2>>引用三大特性 3.3>>引用的使用 3.4>>const引用 3.5>>指针与引用的关系 4>>inline内联函数 5>>nullptr 6>>类和对象(上&#…...
如何为PDF文件创建口令密码
介绍Adobe Acrobat https://helpx.adobe.com/cn/acrobat/using/access-acrobat-across-web-mobile-desktop.html 使用Adobe Acrobat软件添加口令...
【ubuntu】开机进入initramfs,无法开机
Step 1 blkid查看 ext4 的磁盘 Step 2 找到TYPE"EXT4"的盘,我们此处是 /dev/mapper/ubuntu–vg-ubuntu–lv,fsck命令是用于检查和修复Linux文件系统中的错误。通过使用-t参数指定文件系统类型(例如ext4)。我们使用如下命令进行…...
java基础---反射
仅供个人学习使用 1. 什么是反射 Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为…...
CircuitBreaker机制详解:Elasticsearch中的资源管理
CircuitBreaker机制详解:Elasticsearch中的资源管理 在现代软件架构中,熔断器(CircuitBreaker)是一种重要的模式,用于防止系统过载并保护系统稳定性。在Elasticsearch中,熔断器机制尤其关键,因为它们帮助管理资源使用,防止节点因资源耗尽而崩溃。本文将深入探讨Elasti…...
毕氏完美数
毕达哥拉斯 概要 2 \sqrt{2} 2 a b , a < b , a > b ab,a<b,a>b ab,a<b,a>b 判断完美数验证 自守数验证 水仙花数代码验证 概要 回顾完美数,自守数,水仙花数,根号2感受最美公式。 2 \sqrt{2} 2 毕达哥拉斯创立了一…...
数据结构-8.Java. 七大排序算法(中篇)
本篇博客给大家带来的是排序的知识点, 由于时间有限, 分两天来写, 中篇主要实现后三种排序算法: 冒泡排序,快速排序,下一篇讲 归并排序. 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作…...
如何能让安全责任更清晰——构建清晰安全责任体系策略与实践
安全已成为各行各业不可忽视的重要议题。然而,要确保组织的安全运行,仅仅有安全意识是不够的,还需要有一套清晰明确的安全责任体系来支撑。这套体系能够明确每个人的安全职责,促进安全管理工作的有序进行,降低事故发生…...
VBA技术资料MF228:移动形状并覆盖某单元格区域
我给VBA的定义:VBA是个人小型自动化处理的有效工具。利用好了,可以大大提高自己的工作效率,而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套,分为初级、中级、高级三大部分,教程是对VBA的系统讲解&#…...
《Python基础》之基本数据类型
目录 基本数据类型 1、Number(数字) (1)、整数(int) (2)、浮点数(float) (3)、复数(complex) (4)、 布尔…...
2024 APMCM亚太数学建模C题 - 宠物行业及相关产业的发展分析和策略(详细解题思路)
在当下, 日益发展的时代,宠物的数量应该均为稳步上升,在美国出现了下降的趋势, 中国 2019-2020 年也下降,这部分变化可能与疫情相关。需要对该部分进行必要的解释说明。 问题 1: 基于附件 1 中的数据及您的团队收集的…...
66 mysql 的 表自增长锁
前言 mysql 的表锁之 AUTO_INC, 是我们自增长的时候做并发控制的锁 主要是用于 自增长生成新的 id 的时候的控制 在前面的文档中, 我们又看到 mysql 这边自增长的处理的相关的大概脉络 但是 对于一些 并发控制的细节, 我们当时 应该是直接忽略掉了 我们这里就来看一下…...
java中的this关键字
🎉🎉🎉欢迎来到我的博客,我是一名自学了2年半前端的大一学生,熟悉的技术是JavaScript与Vue.目前正在往全栈方向前进, 如果我的博客给您带来了帮助欢迎您关注我,我将会持续不断的更新文章!!!🙏🙏🙏 文章目录…...
资源控制器--laravel进阶篇
laravel的控制器当中有个资源控制器,这个比较好用。 创建资源控制器 php artisan make:controller PhotoController --resource 创建个路由来使用该资源控制器 use App\Http\Controllers\PhotoController; Route::resource(photos, PhotoController::class); 隐式模型绑定不…...
智能工厂的设计软件 为了监管控一体化的全能Supervisor 的监督学习 之 序7 进化论及科学的信息技术创新:分布式账本/区块链/智能合约
Q&A Q46、 聊聊“分布式账本”“区块链”和“智能合约” “分布式账本”、“区块链”和“智能合约”是现代信息技术领域的几个重要概念,它们在金融、供应链管理、物联网等多个领域都发挥着重要作用。以下是对这三个概念的详细解析: 分布式账本 …...
从零开始认识显卡
显卡(GPU,全称为Graphics Processing Unit),是电脑中专门负责图形处理的硬件组件。以下是从零开始认识显卡的简单介绍: 1. 显卡的基本组成 显卡通常由以下几个主要部分组成: GPU核心:显卡的“…...
什么是计算机网络
什么是计算机网络? 计算机网络的定义计算机网络的分类按覆盖范围分类按拓扑结构分类按通信传输介质分类按信号频带占用方式分类 计算机网络的功能信息交换资源共享分布式处理 计算机网络的组成计算机网络的定义计算机网络的分类按覆盖范围分类按拓扑结构分类按通信传…...
网络安全在线网站/靶场:全面探索与实践
目录 1. CyberPatriot 简介 功能与特点 适用人群 2. Hack The Box 简介 功能与特点 适用人群 3. OverTheWire 简介 功能与特点 适用人群 4. VulnHub 简介 功能与特点 适用人群 5. PortSwigger Web Security Academy 简介 功能与特点 适用人群 6. TryHackM…...
多旋翼无人机长航时远距离集群技术详解
多旋翼无人机长航时远距离集群技术是当前无人机技术发展的重要方向之一,它结合了多旋翼无人机的灵活性和集群技术的优势,实现了无人机在长时间、远距离条件下的高效协同作业。以下是对该技术的详细解析: 一、多旋翼无人机特点 多旋翼无人机以…...