目录
- [1. 网络计算器整体逻辑](#1. 网络计算器整体逻辑)
- [2. 重点步骤实现思想](#2. 重点步骤实现思想)
-
- [2.1 自己定制传输协议](#2.1 自己定制传输协议)
- [2.2 关于流式数据的处理](#2.2 关于流式数据的处理)
- [3. 完整版代码实现](#3. 完整版代码实现)
-
- [1. 服务端实现 main.cc](#1. 服务端实现 main.cc)
- [2. 客户端实现 TcpClient.cc](#2. 客户端实现 TcpClient.cc)
- [3. 套接字自定义实现 Socket.hpp](#3. 套接字自定义实现 Socket.hpp)
- [4. 计算器实现 NetCal.hpp](#4. 计算器实现 NetCal.hpp)
- [5. 序列化与反序列化&编解码实现 Protocol.hpp](#5. 序列化与反序列化&编解码实现 Protocol.hpp)
- [6. 自定义守护进程化 Daemon.hpp](#6. 自定义守护进程化 Daemon.hpp)
- [4. 功能验证](#4. 功能验证)
- [5. 完整代码](#5. 完整代码)
1. 网络计算器整体逻辑
1. 服务端执行流程main.cc
- 建立守护进程(调用自定义Daemon.hpp中的Daemon类)
忽略IO, 子进程退出等待相关的信号
父进程直接结束
进程组的组长(原父进程被杀死了),运行到此处的只能是变成孤儿的子进程,并且子进程的新父进程是"1"。
取消守护进程和键盘、显示器的关联
- 启用日志输出到文件模式
- 实例化计算器的类cal(NetCal.hpp中的Cal类)
- 在计算器类中实例化应答类Response(Protocol.hpp中的Response类)
- 实例化cal计算器类
- 实例化协议层的类protocol(Protocol.hpp中的Protocol类)
传入lambda函数给Protocol类中的_func(lambda函数创建Request &req对象,执行cal类中的Execute(req)函数之后返回)
- 实例化服务器类tsvr(TcpServer.hpp中的TcpServer类)
- 传入两个参数:一是端口号,二是lambda函数给TcpServer类中的_service(lambda函数执行protocol中的GetRequest函数)
- 调用tsvr的Start()函数运行程序
- TcpServer tsvr对象中的Start()函数:进行网络字节序转换之后,调用Accept函数进行阻塞等待接收链接。连接成功之后:关闭子进程的套接字文件描述符,创建孙子进程(孤儿)并执行_service()函数
- _service就是:调用protocol对象的GetRequest函数,来获取请求。(在Protocol.hpp中)
- 循环执行sock套接字中的Recv函数,将接收到的数据存放在buffer_queue数据缓冲区中
- 对数据进行解码、反序列化
- 执行之前给protocol的_func传入的lambda函数:调用Cal类中的Execute函数计算结果并返回一个Response的对象resp
- 将返回的resp结果对象序列化、编码并发送给客户端
2.客户端执行流程TcpClient.cc
- 创建客户端套接字(使用Socket类创建其子类TcpSocket的套接字,调用基类函数实现套接字的一系列基础创建功能)
- 与服务端通过Connect建立连接
- 实例化协议层的类protocol(Protocol.hpp中的Protocol类)
客户端的protocol协议不需要传入函数,因为客户端使用的Procotol类中的函数GetResponse函数没有使用_func函数
建立resp_buffer字符串接收缓冲区
- 进入while循环
调用TcpClient.cc文件中的GetDataFromStdin函数,输入需要进行计算的变量和计算方法(加减乘除)
调用protocol对象中的BuildRequestString函数,进行序列化和编码
通过client->Send给服务端发送请求
调用protocol中的GetResponse函数等待获取应答,GetResponse函数中:
实例化Response resp对象,准备接收解码&反序列化之后的结果信息
调用sock中的Recv函数阻塞等待应答信息,并使用resp_buffer缓冲区接收应答信息
将缓冲区中的数据进行解码&反序列化,将结果传给输出型参数resp
调用resp.ShowResult函数进行数据输出
2. 重点步骤实现思想
2.1 自己定制传输协议
使用C++的jsoncpp库,将需要传输的数据转换成json数据,再对数据进行编码(添加数据长度和\r\n)

2.2 关于流式数据的处理
- 你如何保证你每次读取就能读完请求缓冲区的所有内容?答:在Protocol.hpp中的Protocol类中定义的GetResponse和GetRequest函数中的解码读取步骤使用while循环,直到读取的不是一条完整数据为止!(完整数据,指的就是2.1所示)
- 你怎么保证读取完毕或者读取没有完毕的时候,读到的就是一个完整的请求呢?答:通过多次判断,及字符串长度计算得到是否是2.1所示的一条完整数据请求
所以,处理TCP缓冲区中的数据,一定要保证正确处理请求。完整的处理过程应该是:

3. 完整版代码实现
1. 服务端实现 main.cc
TcpServer.hpp
cpp
#include "Socket.hpp"
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <functional>
using namespace SocketModule;
using namespace LogModule;
using ioservice_t = std::function<void(std::shared_ptr<Socket> &sock, InetAddr &client)>;
// 主要解决:连接问题,IO通信的问题
// 细节:TcpServer,需不需要关心自己未来传递的信息是什么?不需要关心
// 网络版本的计算器,长服务
class TcpServer
{
public:
TcpServer(uint16_t port, ioservice_t service)
: _port(port),
_listensockptr(std::make_unique<TcpSocket>()),
_isrunning(false),
_service(service)
{
// TcpSocket类将 套接字创建、绑定、监听封装,调用BuildTcpSocketMethod函数即可完成以上三步
_listensockptr->BuildTcpSocketMethod(_port);
}
void Start()
{
_isrunning = true;
while(_isrunning)
{
InetAddr client;
auto sock = _listensockptr->Accept(&client);
if(sock == nullptr)
{
continue;
}
LOG(LogLevel::DEBUG) << "accept success ...";
// sock && client
pid_t id = fork();
if(id < 0)
{
LOG(LogLevel::FATAL) << "fork error ...";
exit(FORK_ERR);
}
else if(id == 0)
{
// 子进程 -> listensock
_listensockptr->Close();
if(fork() > 0)
exit(OK);
_service(sock, client);
exit(OK);
}
else
{
// 父进程 -> sock
sock->Close();
pid_t rid = ::waitpid(id, nullptr, 0);
(void)rid;
}
_isrunning = false;
}
}
~TcpServer() {}
private:
uint16_t _port;
std::unique_ptr<Socket> _listensockptr;
bool _isrunning;
ioservice_t _service;
};
main.cc
cpp
#include "NetCal.hpp"
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include "Daemon.hpp"
#include <memory>
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " proc" << std::endl;
}
// 我的代码为什么要这一样写?分层解耦!
// ./tcpserver 8080
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::cout << "服务器已经启动,已经是一个守护进程了" << std::endl;
Daemon(0, 0);
// daemon(1, 1);
// Enable_Console_Log_Strategy();
Enable_File_Log_Strategy();
// 1.顶层
std::unique_ptr<Cal> cal = std::make_unique<Cal>();
// 2.协议层
std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>(
[&cal](Request &req)->Response
{
return cal->Execute(req);
});
// 3.服务器层
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::stoi(argv[1]),
[&protocol](std::shared_ptr<Socket> &sock, InetAddr &client)
{
protocol->GetRequest(sock, client);
});
tsvr->Start();
return 0;
}
2. 客户端实现 TcpClient.cc
-
创建套接字
-
向目标服务器发起连接
-
连接成功:通过while循环进行消息的发送和接收
TcpClient.cc
cpp
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
#include "Common.hpp"
#include "Protocol.hpp"
// 1.创建套接字
// 2.向目标服务器发起连接
// 3.连接成功:通过while循环进行消息的发送和接收
using namespace SocketModule;
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}
// 获取输入信息,使用输出型参数
void GetDataFromStdin(int *x, int *y, char *oper)
{
std::cout << "Please Enter x: ";
std::cin >> *x;
std::cout << "Please Enter y: ";
std::cin >> *y;
std::cout << "Please Enter oper: ";
std::cin >> *oper;
}
// ./tcpclient server_ip server_port
int main(int argc, char *argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
std::shared_ptr<Socket> client = std::make_unique<TcpSocket>();
client->BuildTcpClientSocketMethod();
if(client->Connect(server_ip, server_port) != 0)
{
// 成功
std::cerr << "Connect error" << std::endl;
exit(CONNECT_ERR);
}
std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();
std::string resp_buffer;
// 连接服务器成功
while(true)
{
// 1.从标准输入中获取数据
int x, y;
char oper;
GetDataFromStdin(&x, &y, &oper);
// 2.构建一个请求(添加数据长度之后的完整报文,即可以直接发送的字符串)
std::string req_str = protocol->BuildRequestString(x, y, oper);
// 3.发送请求
client->Send(req_str);
// 4.获取应答
Response resp;
bool res = protocol->GetResponse(client, resp_buffer, &resp);
if(res == false)
break;
// 5.显示结果
resp.ShowResult();
}
client->Close();
return 0;
}
3. 套接字自定义实现 Socket.hpp
Socket.hpp
- 通过基类将子类重写(多态)
- 基类通过一个函数,调用套接字创建、绑定、监听函数,一站式实现套接字创建与配置。
cpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
namespace SocketModule
{
using namespace LogModule;
const static int gbacklog = 16;
// 模板方法模式
// 积累socket,大部分方法,都是纯虚方法
class Socket
{
public:
virtual ~Socket() {}
virtual void SocketOrDie() = 0;
virtual void BindOrDie(uint16_t port) = 0;
virtual void ListenOrDie(int backlog) = 0;
virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0;
virtual void Close() = 0;
virtual int Recv(std::string *out) = 0;
virtual int Send(const std::string &message) = 0;
virtual int Connect(const std::string &server_ip, uint16_t port) = 0;
public:
// 执行服务端的: 套接字创建、绑定、监听功能
void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
void BuildTcpClientSocketMethod()
{
SocketOrDie();
}
};
const static int defaultfd = -1;
// 创建Tcp通信Socket类
class TcpSocket : public Socket
{
public:
TcpSocket() : _sockfd(defaultfd)
{}
TcpSocket(int fd) : _sockfd(fd)
{}
~TcpSocket() {}
// 重写SocketOrDie套接字创建
void SocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if(_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
}
LOG(LogLevel::INFO) << "socket success";
}
// 重写BindOrDie套接字地址绑定
void BindOrDie(uint16_t port) override
{
InetAddr localaddr(port); // 创建服务端套接字地址sockaddr_in
int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
if(n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success";
}
// 重写ListenOrDie套接字监听
void ListenOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if(n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success";
}
// 接收套接字地址
std::shared_ptr<Socket> Accept(InetAddr *client) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd = ::accept(_sockfd, CONV(peer), &len);
if(fd < 0)
{
LOG(LogLevel::WARNING) << "accept warning ...";
return nullptr;
}
client->SetAddr(peer);
return std::make_shared<TcpSocket>(fd);
}
// n == read 的返回值 // out是输出型参数
int Recv(std::string *out) override
{
// 流式读取,不关心读到的是什么
char buffer[1024];
ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer)-1, 0);
if(n > 0)
{
buffer[n] = 0;
*out += buffer;
}
return n;
}
int Send(const std::string &message) override
{
return send(_sockfd, message.c_str(), message.size(), 0);
}
// 关闭套接字文件描述符
void Close() override
{
if(_sockfd >= 0)
::close(_sockfd);
}
// 客户端与服务端建立连接
int Connect(const std::string &server_ip, uint16_t port) override
{
InetAddr server(server_ip, port);
return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
}
private:
int _sockfd; // _sockfd
};
}
4. 计算器实现 NetCal.hpp
- 注意除法和求余数,两个计算时要考虑除0的问题
NetCal.hpp
cpp
#pragma once
#include "Protocol.hpp"
#include <iostream>
class Cal
{
public:
Response Execute(Request &req)
{
Response resp(0, 0);
switch (req.Oper())
{
case '+':
resp.SetResult(req.X() + req.Y());
break;
case '-':
resp.SetResult(req.X() - req.Y());
break;
case '*':
resp.SetResult(req.X() * req.Y());
break;
case '/':
{
if (req.Y() == 0)
{
resp.SetCode(1); // 除零错误
}
else
{
resp.SetResult(req.X() / req.Y());
}
}
break;
case '%':
{
if (req.Y() == 0)
{
resp.SetCode(2); // mod 0 错误
}
else
{
resp.SetResult(req.X() % req.Y());
}
}
break;
default:
resp.SetCode(3); // 非法操作
break;
}
return resp;
}
};
5. 序列化与反序列化&编解码实现 Protocol.hpp
- 三个类:请求类、应答类、序列化&反序列化类
cpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <jsoncpp/json/json.h>
#include <functional>
#include "Socket.hpp"
// 实现一个网络版本的计算器
using namespace SocketModule;
// 约定好各个字段的含义:本质就是定好"协议"
// 如何要做序列化和反序列化
// 1.我们自己写(怎么做) ---> 往往不具备很好的扩展性
// 2.使用现成的方案(这个是我们要写的) ---> json -> josncpp
// 请求类 // client -> server
// 1.请求信息 序列化
// 2.请求信息 反序列化
// 3.返回各个变量
class Request
{
public:
Request() {}
Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
{
}
// 1.请求信息 序列化
std::string Serialize()
{
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper;
Json::FastWriter writer;
std::string s = writer.write(root);
return s; // 返回序列化之后的字符串
}
// 2.请求信息 反序列化
bool Deserialize(std::string &in)
{
Json::Value root;
Json::Reader reader;
// 将in字符串中的内容,反序列化(解析)到root中
bool ok = reader.parse(in, root);
if (ok)
{
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
}
return ok;
}
~Request() {}
// 3.返回各个变量
int X() { return _x; }
int Y() { return _y; }
char Oper() { return _oper; }
private:
int _x;
int _y;
char _oper; // 计算符号
};
// 应答类 server -> client
// 1.应答信息 序列化
// 2.应答信息 反序列化
// 3.输出型参数,返回结果
class Response
{
public:
Response() {}
Response(int result, int code) : _result(result), _code(code)
{
}
// 1.应答信息 序列化
std::string Serialize()
{
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
return writer.write(root);
}
// 2.应答信息 反序列化
bool Deserialize(std::string &in)
{
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_result = root["result"].asInt();
_code = root["code"].asInt();
}
return ok;
}
~Response() {}
// 3.输出型参数,返回结果
void SetResult(int res)
{
_result = res;
}
void SetCode(int code)
{
_code = code;
}
void ShowResult()
{
std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;
}
private:
int _result; // 运算结果,无法区分清除是应答是: 计算结果,还是异常值
int _code; // 0:success, 1,2,3,4 -> 不同的预算暖异常情况
};
const std::string sep = "\r\n";
using func_t = std::function<Response(Request &req)>;
// 协议 (基于TCP)需要解决两个问题
// 1.request和response必须得有序列化和反序列化功能
// 2.你必须保证,读取的时候,读到完整的请求(TCP, UDP不用考虑)
// Protocol协议功能
// 1.编码
// 2.解码
// 3.接收请求处理后回传结果
// 4.发送请求并接收结果
class Protocol
{
public:
Protocol()
{
}
Protocol(func_t func) : _func(func)
{
}
// 1.编码格式
std::string Encode(const std::string &jsonstr)
{
std::string len = std::to_string(jsonstr.size());
return len + sep + jsonstr + sep;
}
// 编码格式: 字符串长度 + \r\n + 字符串 + \r\n
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
// 5
// 50
// 50\r
// 50\r\n
// 50\r\n{"x": 10, "
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n50\r\n{"x": 10, "y" : 20, "ope
//.....
// packge故意是&
// a. 判断报文完整性
// b. 如果包含至少一个完整请求,提取他, 并从移除它,方便处理下一个
// 2.解码格式
bool Decode(std::string &buffer, std::string *package)
{
// 1.报文长度len不完整,则退出
ssize_t pos = buffer.find(sep);
if (pos == std::string::npos) // 没有找到第一个\r\n
return false;
std::string package_len_str = buffer.substr(0, pos);
int package_len_int = std::stoi(package_len_str);
// 2.buffer一定有长度,但是不一定是完整的报文,还需要继续判断
int target_len = package_len_str.size() + package_len_int + 2 * sep.size();
if (buffer.size() < target_len) // 报文不完整 len\r\n后面的信息不完整
return false;
// 3.buffer一定至少有一个完整的报文
// 读取完整的一条报文数据:从数据头读取len个字符
// *package为输出型参数
*package = buffer.substr(pos + sep.size(), package_len_int);
buffer.erase(0, target_len); // 头删读取完的字符串
return true;
}
// 3.接收请求处理后回传结果
void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
{
// 读取
std::string buffer_queue; // 整个服务端的数据缓冲区
while (true)
{
int n = sock->Recv(&buffer_queue); // 我认为: Recv函数应该返回buffer.size()才对
if (n > 0)
{
std::cout << "-----------request_buffer--------------" << std::endl;
std::cout << buffer_queue << std::endl;
std::cout << "------------------------------------" << std::endl;
// 1.解析报文,提取完整的json请求: 如果有一条完整的报文,则通过json_package输出;如果不完整,就让服务器继续读取
// 指导解析完所有的报文为止
std::string json_package;
while (Decode(buffer_queue, &json_package))
{
// std::cout << "-----------request_json--------------" << std::endl;
// std::cout << json_package << std::endl;
// std::cout << "------------------------------------" << std::endl;
// std::cout << "-----------request_buffer--------------" << std::endl;
// std::cout << buffer_queue << std::endl;
// std::cout << "------------------------------------" << std::endl;
LOG(LogLevel::DEBUG) << client.StringAddr() << "请求: " << json_package;
// 此处一定拿到了一条完整的报文
// 2.请求json,反序列化(一般不会失败)
Request req;
bool ok = req.Deserialize(json_package);
if (!ok)
continue;
// 3.我一定得到了一个内部属性已经被设置好的req请求了
// 将req中的数据通过_func函数进行处理,结果给resp应答对象
Response resp = _func(req);
// 4.序列化: 将应答对象resp序列化
std::string json_str = resp.Serialize();
// 5.编码格式: 添加自定义长度
std::string send_str = Encode(json_str); // 携带长度的应答报文了"len\r\n{result:XXX, code:XX}\r\n"
// 6.发送
sock->Send(send_str);
}
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "client:" << client.StringAddr() << "Quit!";
break;
}
else
{
LOG(LogLevel::WARNING) << "client:" << client.StringAddr() << ", recv, error";
break;
}
}
}
// 4.发送请求并接收结果
bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
{
// 面向字节流,怎么保证client读取到的 一个网络字符串,一定是一个完整的请求呢?
while (true)
{
int n = client->Recv(&resp_buff); // 整个客户的接收应答结果的数据缓冲区
if (n > 0)
{
// std::cout << "-----------resp_buffer--------------" << std::endl;
// std::cout << resp_buff << std::endl;
// std::cout << "------------------------------------" << std::endl;
// 1.解析报文,提取完整的json请求: 如果有一条完整的报文,则通过json_package输出;如果不完整,就让服务器继续读取
// 指导解析完所有的报文为止
std::string json_package;
while (Decode(resp_buff, &json_package))
{
// std::cout << "----------response json---------------" << std::endl;
// std::cout << json_package << std::endl;
// std::cout << "--------------------------------------" << std::endl;
// std::cout << "-----------resp_buffer--------------" << std::endl;
// std::cout << resp_buff << std::endl;
// std::cout << "------------------------------------" << std::endl;
// 此处一定拿到了一条完整的报文
// 2.请求json,反序列化(一般不会失败)
resp->Deserialize(json_package);
}
return true;
}
else if (n == 0)
{
std::cout << "server quit " << std::endl;
return false;
}
else
{
std::cout << "recv error" << std::endl;
return false;
}
}
}
std::string BuildRequestString(int x, int y, char oper)
{
// 1.建立一个完整的请求
Request req(x, y, oper);
// 2.序列化
std::string json_req = req.Serialize();
// // 2.1 debug
// std::cout << "------------json_req string------------" << std::endl;
// std::cout << json_req << std::endl;
// std::cout << "---------------------------------------" << std::endl;
// 3.添加长度报文
return Encode(json_req);
}
~Protocol()
{
}
private:
// 因为我们用的是多进程
// Request _req;
// Response _resp;
func_t _func;
};
6. 自定义守护进程化 Daemon.hpp
-
忽略IO, 子进程退出等待相关的信号
-
父进程直接结束
-
进程组的组长(原父进程被杀死了),运行到此处的自能是变成孤儿的子进程,并且子进程的新父进程是"1"。
-
取消守护进程和键盘、显示器的关联
Daemon.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <cstdint>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include "Log.hpp"
#include "Common.hpp"
using namespace LogModule;
const std::string dev = "/dev/null";
// 将服务进行守护进程化
// 1.忽略IO, 子进程退出等待相关的信号
// 2.父进程直接结束
// 3.进程组的组长(原父进程被杀死了),运行到此处的自能是变成孤儿的子进程,并且子进程的新父进程是"1"。
// 4.取消守护进程和键盘、显示器的关联
void Daemon(int nochdir, int noclose)
{
// 1.忽略IO, 子进程退出等待相关的信号
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN); // SIG_DFL
// 2.父进程直接结束
if (fork() > 0)
exit(0);
// 3.进程组的组长(原父进程被杀死了),运行到此处的自能是变成孤儿的子进程,并且子进程的新父进程是"1"。
setsid();
if(nochdir == 0) // 更改进程的工作路径(前一节已经讲了)
chdir("/");
// 4.依旧可能显示器、键盘,stdin、stdout、stderr关联的
// 守护进程,不从键盘输入,也不需要向显示器打印
// 方法1:关闭0,1,2 -- 不推荐
// 方法2:打开/dev/null,重定向标准输入,标准输出,标准错误到/dev/null
if(noclose == 0)
{
int fd = ::open(dev.c_str(), O_RDWR);
if(fd < 0)
{
LOG(LogLevel::FATAL) << "open " << dev << " errno";
exit(OPEN_ERR);
}
else
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
}
4. 功能验证
启动ServerNetCald之后,启动一个守护进程(之前提到的孙子进程),然后父进程结束。
再查看ServerNetCald的状态,发现此时的孙子进程仍在运行,但不是进程组的组长。
补:按理说ServerNetCald运行之后,他应该只有一个孤儿进程,他的父进程应该是1。但是在我的虚拟机上运行,他的父进程不是1(我也验证了我老师的代码,运行之后和我的输出一样),但是我关闭ServerNetCald的父进程,虚拟机就关机了?
