
🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:

文章目录
- [1 ~> 应用层协议与序列化基础](#1 ~> 应用层协议与序列化基础)
-
- [1.1 核心概念](#1.1 核心概念)
- [1.2 请求 / 应答报文设计](#1.2 请求 / 应答报文设计)
-
- [1.2.1 文字描述](#1.2.1 文字描述)
- [1.2.2 Protocol.hpp](#1.2.2 Protocol.hpp)
- [2 ~> TCP 粘包问题与自定义协议](#2 ~> TCP 粘包问题与自定义协议)
-
- [2.1 粘包问题](#2.1 粘包问题)
- [2.2 自定义协议格式](#2.2 自定义协议格式)
- [2.3 封包 / 解包实现](#2.3 封包 / 解包实现)
-
- [2.3.1 Protocol.hpp](#2.3.1 Protocol.hpp)
- [3 ~> 模板方法模式封装 Socket](#3 ~> 模板方法模式封装 Socket)
-
- [3.1 模板方法模式思想](#3.1 模板方法模式思想)
- [3.2 Socket 基类设计](#3.2 Socket 基类设计)
-
- [3.2.1 Socket.hpp](#3.2.1 Socket.hpp)
- [3.3 TcpSocket 实现](#3.3 TcpSocket 实现)
-
- [3.3.1 笔记整理](#3.3.1 笔记整理)
- [3.3.2 Socket.hpp](#3.3.2 Socket.hpp)
- [3.4 InetAddr 地址封装](#3.4 InetAddr 地址封装)
-
- [3.4.1 笔记整理](#3.4.1 笔记整理)
- [3.4.2 InetAddr.hpp](#3.4.2 InetAddr.hpp)
- [4 ~> TcpServer 服务端实现](#4 ~> TcpServer 服务端实现)
-
- [4.1 多进程 + 回调架构](#4.1 多进程 + 回调架构)
- [4.2 TcpServer.hpp](#4.2 TcpServer.hpp)
- [5 ~> 三层架构:协议层 + 业务层 + 网络通信层](#5 ~> 三层架构:协议层 + 业务层 + 网络通信层)
-
- [5.1 三层架构设计](#5.1 三层架构设计)
- [5.2 协议层处理入口](#5.2 协议层处理入口)
-
- [5.2.1 笔记整理](#5.2.1 笔记整理)
- [5.2.2 Protocol.hpp](#5.2.2 Protocol.hpp)
- [5.3 业务层:计算器实现](#5.3 业务层:计算器实现)
-
- [5.3.1 笔记整理](#5.3.1 笔记整理)
- [5.3.2 Calculator.hpp](#5.3.2 Calculator.hpp)
- [6 ~> 理解应用层(OSI七层结构中的上三层)](#6 ~> 理解应用层(OSI七层结构中的上三层))
-
- [6.1 为什么OSI的上三层都叫"应用层"?](#6.1 为什么OSI的上三层都叫“应用层”?)
- [6.2 为什么操作系统只负责到传输层?](#6.2 为什么操作系统只负责到传输层?)
- [6.3 面对"十几个请求同时到达"该如何处理?](#6.3 面对“十几个请求同时到达”该如何处理?)
-
- [6.3.1 报文维度的处理:TCP粘包与解析(表示层)](#6.3.1 报文维度的处理:TCP粘包与解析(表示层))
- [6.3.2 并发维度的处理:I/O多路复用与线程池(会话层 / 架构层)](#6.3.2 并发维度的处理:I/O多路复用与线程池(会话层 / 架构层))
- [7 ~> 客户端(OnlineCalClient.cc)的读 / 写新接口](#7 ~> 客户端(OnlineCalClient.cc)的读 / 写新接口)
-
- [7.1 读](#7.1 读)
- [7.2 写](#7.2 写)
- [7.3 OnlineCalClient.cc中的发送和接受应答](#7.3 OnlineCalClient.cc中的发送和接受应答)
- [7 ~> 工具类:日志 + 互斥锁](#7 ~> 工具类:日志 + 互斥锁)
-
- [7.1 互斥锁(Mutex.hpp)](#7.1 互斥锁(Mutex.hpp))
-
- [7.1.1 笔记整理](#7.1.1 笔记整理)
- [7.1.2 源码](#7.1.2 源码)
- [7.2 日志系统(Logger.hpp)](#7.2 日志系统(Logger.hpp))
-
- [7.2.1 笔记整理](#7.2.1 笔记整理)
- [7.2.2 源码](#7.2.2 源码)
- [8 ~> 守护进程](#8 ~> 守护进程)
-
- [8.1 守护进程整理](#8.1 守护进程整理)
- [8.2 回顾:前台作业 VS 后台作业](#8.2 回顾:前台作业 VS 后台作业)
- [8.3 认识守护进程的新系统调用:setsid()](#8.3 认识守护进程的新系统调用:setsid())
- [8.4 Daemon.hpp](#8.4 Daemon.hpp)
- [8.5 守护进程的细节问题](#8.5 守护进程的细节问题)
- [8.6 父进程(一般进程)和子进程(守护进程)](#8.6 父进程(一般进程)和子进程(守护进程))
- [8.7 补充](#8.7 补充)
-
- [8.7.1:测试 && 项目部署过程](#8.7.1:测试 && 项目部署过程)
- [8.7.2 补充2:部署是什么?](#8.7.2 补充2:部署是什么?)
- [8.7.3 补充3:是否要更改工作路径 && 是否要进行重定向](#8.7.3 补充3:是否要更改工作路径 && 是否要进行重定向)
- [8.8 我是不是一定要手搓一个守护进程代码?](#8.8 我是不是一定要手搓一个守护进程代码?)
- [8.9 守护进程总结](#8.9 守护进程总结)
- [9 ~> 拓展:了解nohup命令](#9 ~> 拓展:了解nohup命令)
-
- [9.1 飞书笔记链接](#9.1 飞书笔记链接)
- 结尾

1 ~> 应用层协议与序列化基础

1.1 核心概念
- 应用层 :程序员编写的、解决实际需求的网络程序都运行在应用层,应用层的核心是自定义协议。
- 协议 :通信双方提前约定好的结构化数据格式,是数据传输的 "共同语言"。
- 序列化 :把内存中的结构化数据 转换成可在网络中传输的字符串 / 字节流 ,即多变一。
- 反序列化 :把网络中收到的字节流 / 字符串 还原成内存中的结构化数据 ,即一变多。
- 技术选型 :艾莉丝这里的网络计算器 是使用jsoncpp库实现序列化与反序列化,可读性强、跨语言、开发效率高。
- 通信本质 :网络通信只关心字节流,不关心数据具体含义,协议负责定义数据含义。
1.2 请求 / 应答报文设计
1.2.1 文字描述
- 报文分为请求报文(Client→Server)和应答报文(Server→Client)。
- 请求报文包含:两个操作数
_x/_y、操作符_oper(支持+ - * / %)。 - 应答报文包含:计算结果
_result、状态码_exitcode。 - 状态码约定:
0=计算成功、1=除0错误、2=模0错误、3=操作码非法。 - 报文必须实现Serialize (序列化)和Deserialize(反序列化)接口。
1.2.2 Protocol.hpp
cpp
// 请求报文: client -> server
class Request
{
public:
Request(){}
Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) {}
// 序列化:结构化对象 → JSON字符串(多变一)
void Serialize(std::string *outstr)
{
Json::Value root;
root["datax"] = _x;
root["datay"] = _y;
root["oper"] = _oper;
Json::FastWriter writer; // 紧凑格式,适合网络传输
*outstr = writer.write(root);
}
// 反序列化:JSON字符串 → 结构化对象(一变多)
void Deserialize(std::string &instr)
{
Json::Value root;
Json::Reader reader;
if(reader.parse(instr, root))
{
_x = root["datax"].asInt();
_y = root["datay"].asInt();
_oper = root["oper"].asInt();
}
else std::cout << "bug!!! 反序列化失败" << std::endl;
}
public:
int _x; // 左操作数
int _y; // 右操作数
char _oper; // 操作符
};
// 应答报文: server->client
class Response
{
public:
Response() : _result(0), _exitcode(0) {}
// 应答序列化
void Serialize(std::string *outstr)
{
Json::Value root;
root["result"] = _result;
root["exitcode"] = _exitcode;
Json::FastWriter writer;
*outstr = writer.write(root);
}
// 应答反序列化
void Deserialize(std::string &instr)
{
Json::Value root;
Json::Reader reader;
if(reader.parse(instr, root))
{
_result = root["result"].asInt();
_exitcode = root["exitcode"].asInt();
}
else std::cout << "bug!!! 反序列化失败" << std::endl;
}
public:
int _result; // 计算结果
int _exitcode; // 状态码:0可信,1除0,2模0,3操作码错误
};
2 ~> TCP 粘包问题与自定义协议
2.1 粘包问题
- TCP 本质 :面向字节流 的传输协议,没有报文边界。
- 粘包原因 :客户端
write次数 ≠ 服务端read次数,多个报文会被粘连 或截断。 - 危害:直接解析 JSON 会失败、程序崩溃、数据错乱。
- 解决方案 :应用层自定义协议 ,手动定义报文边界,保证报文完整性。
2.2 自定义协议格式
协议格式:报文长度 + \r\n + JSON 串 + \r\n
设计思路:先读长度字段 ,知道完整报文长度,再读取对应长度的数据,确保报文完整。
分隔符:使用\r\n,符合 HTTP 等通用协议,不易与 JSON 数据冲突。
2.3 封包 / 解包实现
- 封包Package :给序列化后的 JSON 串添加长度 + 分隔符,组装成符合协议的报文。
- 解包UnPackage :从字节流中提取完整 JSON 串,判断报文是否完整。
- 解包返回值:
>0= 提取到完整报文、0= 报文不完整、<0= 协议解析错误。 - 安全校验:长度字段必须是纯数字,防止非法数据导致stoi崩溃。
2.3.1 Protocol.hpp
cpp
const std::string gsep = "\r\n"; // 协议分隔符
// 封包:JSON字符串 → 完整协议报文
std::string Package(const std::string jsonstr)
{
uint32_t len = jsonstr.size();
// 拼接格式:长度\r\nJSON串\r\n
return std::to_string(len) + gsep + jsonstr + gsep;
}
// 解包:字节流 → 提取完整JSON串
int UnPackage(std::string &streamstr, std::string *jsonstr)
{
// 1. 找第一个分隔符,获取长度字段
auto pos = streamstr.find(gsep);
if(pos == std::string::npos) return 0; // 未找到分隔符,报文不完整
// 2. 提取长度字符串,校验纯数字
std::string packlenstr = streamstr.substr(0, pos);
for(auto &c : packlenstr)
if(c < '0' || c > '9') return -1; // 非法长度,协议错误
// 3. 计算完整报文总长度
uint32_t packlenint = std::stoi(packlenstr);
uint32_t targetlen = packlenstr.size() + packlenint + 2 * gsep.size();
// 4. 缓冲区数据不足,报文不完整
if(streamstr.size() < targetlen) return 0;
// 5. 提取JSON串,移除已处理报文(出队列)
*jsonstr = streamstr.substr(pos + gsep.size(), packlenint);
streamstr.erase(0, targetlen);
return targetlen;
}
3 ~> 模板方法模式封装 Socket
3.1 模板方法模式思想
- 设计模式 :模板方法模式,定义算法骨架,具体步骤延迟到子类实现。
- 设计目的 :统一 TCP/UDP/Unix 域套接字的创建流程,代码复用、解耦、易扩展。
- 核心 :基类
Socket定义纯虚接口 ,模板方法组合接口流程;子类TcpSocket重写接口。 - 优势 :新增套接字类型只需继承基类,无需修改原有代码,符合开闭原则。
3.2 Socket 基类设计
- 基类定义套接字必备操作:创建、绑定、监听、接收、连接、收发数据、关闭。
- 模板方法:
BuildSocketMethod:服务端套接字流程(创建→绑定→监听)。BuildClientSocketMethod:客户端套接字流程(创建→连接)。
- 析构函数设为虚函数,保证子类析构被正确调用。
3.2.1 Socket.hpp
cpp
class Socket
{
public:
virtual ~Socket(){} // 虚析构,防止内存泄漏
// 纯虚接口,子类必须实现
virtual void CreateSocketOrDie() = 0;
virtual void BindSocketOrDie(uint16_t port) = 0;
virtual void ListenSocketOrDie() = 0;
virtual std::shared_ptr<Socket> Accepter(InetAddr *clientaddr) = 0;
virtual void ConnectOrDie(const std::string &serverip, uint16_t serverport) = 0;
virtual int Socketfd() = 0;
virtual void Close() = 0;
virtual int Recv(std::string *outstr) = 0;
virtual int Send(const std::string &outstr) = 0;
public:
// 模板方法:服务端套接字创建流程(骨架固定)
void BuildSocketMethod(uint16_t port)
{
CreateSocketOrDie();
BindSocketOrDie(port);
ListenSocketOrDie();
}
// 模板方法:客户端套接字创建流程
void BuildClientSocketMethod(const std::string &serverip, uint16_t serverport)
{
CreateSocketOrDie();
ConnectOrDie(serverip, serverport);
}
};
3.3 TcpSocket 实现
3.3.1 笔记整理
- 继承
Socket基类,重写所有纯虚接口,实现 TCP 套接字具体操作。 CreateSocketOrDie:创建 TCP 套接字,失败直接退出。BindSocketOrDie:绑定端口号,失败直接退出。ListenSocketOrDie:启动监听,队列长度gbacklog=16。Accepter:接收客户端连接,返回新套接字的智能指针。Recv/Send:封装recv/send系统调用,Recv自动拼接缓冲区数据。Close:关闭文件描述符,防止资源泄漏。
3.3.2 Socket.hpp
cpp
static const int gbacklog = 16; // 监听队列长度
enum { SOCKET_ERR = 1, BIND_ERR, LISTEN_ERR }; // 错误码
class TcpSocket : public Socket
{
public:
TcpSocket() : _sockfd(-1) {}
TcpSocket(int sockfd) : _sockfd(sockfd) {}
// 创建TCP套接字
void CreateSocketOrDie() override
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(_sockfd < 0) { LOG(LogLevel::FATAL) << "create socket error"; exit(SOCKET_ERR); }
LOG(LogLevel::INFO) << "create socket success";
}
// 绑定端口
void BindSocketOrDie(uint16_t port) override
{
InetAddr local(port);
int n = bind(_sockfd, local.Addr(), local.AddrLen());
if(n < 0) { LOG(LogLevel::FATAL) << "bind socket error"; exit(BIND_ERR); }
LOG(LogLevel::INFO) << "bind socket success";
}
// 监听
void ListenSocketOrDie() override
{
int n = listen(_sockfd, gbacklog);
if(n < 0) { LOG(LogLevel::FATAL) << "listen socket error"; exit(LISTEN_ERR); }
LOG(LogLevel::FATAL) << "listen socket success";
}
// 接收连接
std::shared_ptr<Socket> Accepter(InetAddr *clientaddr) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_sockfd, CONV(&peer), &len);
if(sockfd < 0) return nullptr;
*clientaddr = peer; // 赋值客户端地址
return std::make_unique<TcpSocket>(sockfd);
}
// 接收数据:自动拼接到缓冲区
int Recv(std::string *outstr) override
{
char buffer[1024];
ssize_t n = recv(_sockfd, buffer, sizeof(buffer)-1, 0);
if(n > 0) { buffer[n] = 0; *outstr += buffer; return n; }
else if(n == 0) return 0; // 对端关闭
else return -1; // 读取失败
}
// 发送数据
int Send(const std::string &outstr) override
{
return send(_sockfd, outstr.c_str(), outstr.size(), 0);
}
// 连接服务器
void ConnectOrDie(const std::string &serverip, uint16_t serverport) override
{
InetAddr serveraddr(serverport, serverip);
int n = connect(_sockfd, serveraddr.Addr(), serveraddr.AddrLen());
if(n != 0) { LOG(LogLevel::FATAL) << "connect failed"; return; }
LOG(LogLevel::INFO) << "connect success";
}
// 关闭套接字
void Close() override { if(_sockfd >=0) close(_sockfd); _sockfd = -1; }
// 获取文件描述符
int Socketfd() override { return _sockfd; }
private:
int _sockfd; // TCP套接字文件描述符
};
3.4 InetAddr 地址封装
3.4.1 笔记整理
- 封装
sockaddr_in结构体,统一处理网络字节序↔主机字节序转换。 - 提供
Addr()/AddrLen()方法,直接适配bind/connect/accept系统调用。 - 重载赋值运算符,支持
accept时直接赋值客户端地址。 StringAddress():将地址转为[IP:端口]格式,用于日志打印。
3.4.2 InetAddr.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define CONV(addr) ((struct sockaddr *)(addr)) // 类型转换宏
class InetAddr
{
public:
InetAddr(){}
// 从sockaddr_in构造
InetAddr(struct sockaddr_in &addr) : _net_addr(addr)
{
_port = ntohs(_net_addr.sin_port); // 网络序转主机序
_ip = inet_ntoa(_net_addr.sin_addr);
}
// 从端口+IP构造
InetAddr(uint16_t port, std::string ip = "0.0.0.0"): _port(port), _ip(ip)
{
_net_addr.sin_family = AF_INET;
_net_addr.sin_port = htons(_port); // 主机序转网络序
_net_addr.sin_addr.s_addr = inet_addr(_ip.c_str());
}
uint16_t Port() { return _port; }
std::string Ip() { return _ip; }
struct sockaddr *Addr() { return CONV(&_net_addr); } // 用于系统调用
socklen_t AddrLen() { return sizeof(_net_addr); }
// 赋值重载:accept时直接赋值
void operator=(const struct sockaddr_in &addr)
{
_net_addr = addr;
_port = ntohs(_net_addr.sin_port);
_ip = inet_ntoa(_net_addr.sin_addr);
}
// 转为字符串格式,日志打印
std::string StringAddress() { return "[" + _ip + ":" + std::to_string(_port) + "]"; }
private:
uint16_t _port;
std::string _ip;
struct sockaddr_in _net_addr; // 网络地址结构体
};
4 ~> TcpServer 服务端实现
4.1 多进程 + 回调架构
- 并发模型 :多进程,父进程负责
accept接收连接,子进程负责 IO 处理。 - 优势:子进程崩溃不影响父进程,服务稳定性高。
- 信号处理 :忽略
SIGCHLD信号,避免产生僵尸进程。 - 解耦设计 :网络层只负责 IO,业务逻辑通过回调函数(_cb) 交给上层处理。
- 长连接:客户端连接后可持续发送请求,不主动断开。
4.2 TcpServer.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include "Socket.hpp"
// 回调类型:字节流→应答报文
using handler_t = std::function<std::string(std::string &)>;
class TcpServer
{
public:
TcpServer(uint16_t port, handler_t handler)
: _port(port),_listensockfd(std::make_unique<TcpSocket>()),_handlerstream(handler)
{
// 调用模板方法创建监听套接字
_listensockfd->BuildSocketMethod(_port);
}
// 服务启动
void Run()
{
_isrunning = true;
signal(SIGCHLD, SIG_IGN); // 忽略子进程退出信号,防止僵尸进程
while(_isrunning)
{
InetAddr clientaddr;
// 接收客户端连接
auto sockfd = _listensockfd->Accepter(&clientaddr);
if(!sockfd) { LOG(LogLevel::WARNING) << "accepter error"; continue; }
LOG(LogLevel::DEBUG) << "client addr:" << clientaddr.StringAddress();
// 多进程处理:父进程继续监听,子进程处理IO
if(fork() == 0)
{
_listensockfd->Close(); // 子进程关闭监听套接字
HanderIO(sockfd, clientaddr);
exit(0);
}
sockfd->Close(); // 父进程关闭客户端套接字
}
}
private:
// 处理客户端IO
void HanderIO(std::shared_ptr<Socket> sockfd, InetAddr clientaddr)
{
std::string inbuffer; // 字节流缓冲区,自动拼接
while(true)
{
// 读取数据
int n = sockfd->Recv(&inbuffer);
if(n < 0) { LOG(LogLevel::WARNING) << "recv error"; break; }
if(n == 0) { LOG(LogLevel::INFO) << "client quit"; break; } // 客户端退出
// 回调协议层处理业务
std::string outbuffer;
if(_handlerstream) outbuffer = _handlerstream(inbuffer);
// 发送应答
if(!outbuffer.empty()) sockfd->Send(outbuffer);
}
sockfd->Close();
}
private:
int _port;
std::unique_ptr<Socket> _listensockfd; // 监听套接字
bool _isrunning;
handler_t _handlerstream; // 业务回调
};
5 ~> 三层架构:协议层 + 业务层 + 网络通信层
5.1 三层架构设计
- 网络通信层 :
TcpServer/Socket,负责连接管理、IO 收发。 - 协议层 :
Protocol,负责封包 / 解包、序列化 / 反序列化、粘包解决。 - 业务层 :
Calculator,负责具体的业务逻辑(计算器运算)。 - 解耦方式:回调函数注入,层与层之间互不感知内部实现。
5.2 协议层处理入口
5.2.1 笔记整理
HandlerRequest:协议层入口,循环解包,支持一次处理多个报文。- 流程:解包→反序列化→回调业务→序列化→封包。
- 循环处理:直到报文不完整或解析错误才退出。
5.2.2 Protocol.hpp
cpp
// 业务回调类型:Request→Response
using callback_t = std::function<Response(const Request &)>;
class Protocol
{
public:
Protocol(callback_t cb):_cb(cb){}
// 协议处理入口:字节流→应答报文
std::string HandlerRequest(std::string &streamstr)
{
std::string resp_package;
// 循环解包,处理所有完整报文
while(true)
{
std::string jsonstring;
int n = UnPackage(streamstr, &jsonstring);
if(n == 0) return resp_package; // 报文不完整,返回已处理应答
if(n == -1) { LOG(LogLevel::DEBUG) << "协议解析失败"; exit(1); }
// 反序列化→业务处理→序列化→封包
Request req; req.Deserialize(jsonstring);
Response resp = _cb(req); // 回调业务层
std::string respjsonstr; resp.Serialize(&respjsonstr);
resp_package += Package(respjsonstr); // 拼接应答
}
}
private:
callback_t _cb; // 业务回调
};
5.3 业务层:计算器实现
5.3.1 笔记整理
- 业务层只负责运算逻辑,与网络、协议完全解耦。
- 支持
+ - * / %五种运算。 - 错误处理:除 0、模 0、非法操作码,返回对应状态码。
5.3.2 Calculator.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include "Protocol.hpp"
// 前向声明
class Request;
class Response;
class Calculator
{
public:
Calculator() {}
~Calculator() {}
// 业务核心:根据请求生成应答
Response Exec(const Request &req)
{
Response resp;
switch (req._oper)
{
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._exitcode = 1; // 除0错误
else resp._result = req._x / req._y;
break;
case '%':
if (req._y == 0) resp._exitcode = 2; // 模0错误
else resp._result = req._x % req._y;
break;
default: resp._exitcode = 3; break; // 非法操作码
}
return resp;
}
};
6 ~> 理解应用层(OSI七层结构中的上三层)

6.1 为什么OSI的上三层都叫"应用层"?

6.2 为什么操作系统只负责到传输层?

6.3 面对"十几个请求同时到达"该如何处理?
6.3.1 报文维度的处理:TCP粘包与解析(表示层)

6.3.2 并发维度的处理:I/O多路复用与线程池(会话层 / 架构层)

7 ~> 客户端(OnlineCalClient.cc)的读 / 写新接口
读取的时候自然可以使用read,从指定文件描述符里读取指定缓冲区指定大小的数据,今天我不用,补充使用一组新的接口:
7.1 读

7.2 写

7.3 OnlineCalClient.cc中的发送和接受应答

7 ~> 工具类:日志 + 互斥锁
7.1 互斥锁(Mutex.hpp)
7.1.1 笔记整理
封装pthread_mutex,实现线程安全。LockGuard:RAII 风格,自动加锁 / 解锁,防止死锁。
7.1.2 源码
cpp
#pragma once
#include <iostream>
#include <pthread.h>
// 互斥锁封装
class Mutex
{
public:
Mutex() { pthread_mutex_init(&_lock, nullptr); }
void Lock() { pthread_mutex_lock(&_lock); }
void Unlock() { pthread_mutex_unlock(&_lock); }
~Mutex() { pthread_mutex_destroy(&_lock); }
private:
pthread_mutex_t _lock;
};
// RAII自动锁
class LockGuard
{
public:
LockGuard(Mutex *lockp): _lockp(lockp) { _lockp->Lock(); }
~LockGuard() { _lockp->Unlock(); }
private:
Mutex *_lockp;
};
7.2 日志系统(Logger.hpp)
7.2.1 笔记整理
- 策略模式:支持控制台 / 文件日志输出。
- RAII 日志:析构时自动刷新日志,简化使用。
- 日志格式:时间 + 等级 + 文件名 + 行号 + 信息。
- 宏封装:LOG(level)简化调用。
7.2.2 源码
源码前面已经展示过了,这里不写了,太冗长啦。
8 ~> 守护进程
8.1 守护进程整理
- 守护进程 :脱离终端、会话、用户登录状态,后台长期运行的服务进程。
- 作用:关闭终端不影响服务运行,是网络服务的标准运行方式。
- 实现步骤 :
- 忽略
SIGPIPE/SIGCHLD信号。 fork后父进程退出,确保子进程非进程组组长。setsid创建新会话,脱离原终端。- 重定向标准 IO 到
/dev/null(黑洞文件)。
- 忽略

8.2 回顾:前台作业 VS 后台作业

8.3 认识守护进程的新系统调用:setsid()

8.4 Daemon.hpp
cpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
// 进程守护化
void Daemon()
{
// 1. 忽略信号
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
// 2. 父进程退出,子进程非组长
if(fork() > 0) exit(0);
// 3. 创建新会话,成为守护进程
setsid();
// 4. 重定向标准输入输出到黑洞
int fd = open("/dev/null", O_RDWR);
if(fd >= 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
8.5 守护进程的细节问题

8.6 父进程(一般进程)和子进程(守护进程)
我已经知道:守护进程也是一种孤儿进程------父进程退出了!

8.7 补充
8.7.1:测试 && 项目部署过程



比如网易云音乐:

dll就是动态库。
8.7.2 补充2:部署是什么?
在Linux当中安装软件就叫做部署------所谓的部署是什么?可不可以把这样部署:



8.7.3 补充3:是否要更改工作路径 && 是否要进行重定向

8.8 我是不是一定要手搓一个守护进程代码?
系统说你别做了,我帮你做了:
系统调用接口:

可以自己手搓守护进程也可以不做,不做就调用系统的daemon系统调用接口。

这个逻辑和刚才蛋哥写的是反的。
测试一下:

这里(0,0)是更改工作路径、进行重定向。
路径更改到根目录、重定向到/dev/null:

8.9 守护进程总结
守护进程也叫做精灵进程,本质也是孤儿进程。本质也是后台进程,一般的后台,一定隶属于一个Shell对应的session会话,而守护进程自成进程组、自成会话。
9 ~> 拓展:了解nohup命令

如果你运行了 nohup 却发现当前目录没生成 nohup.out,可能是因为你没有写权限。此时 nohup 会尝试把日志写到 $HOME/nohup.out 中。
9.1 飞书笔记链接
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!"
"技术之路难免有困惑,但同行的人会让前进更有方向。" |
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!
往期回顾:
【Linux网络】Linux 网络编程:应用层自定义协议与序列化(2)序列化与反序列化
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
