子进程入口模板框架
下面把三种“灵活进程入口”方案掰开揉碎讲一遍,让你一眼看出:
代码长啥样
谁最简洁、谁最类型安全、谁最易扩展
什么时候该用谁
方案 1:裸指针 + 固定签名(“C Style”)
// 入口固定死
using TaskFunc = void(*)(void* arg);void worker_main(void* raw) // 子进程入口
{int fd = *(int*)raw; // 先强转再解引用handle_client(fd);
}// 父进程
int client_fd = accept(listenfd, ...);
pid_t pid = fork();
if (pid == 0) { // 子进程worker_main(&client_fd); // 直接调exit(0);
}特点
✅ 最直观,无依赖,C/C++ 都能用
❌ 零类型检查,void* 一传错直接 UB
❌ 参数一多就完蛋,得封装结构体 + 二次强转
适用
极底层、boot-strap 阶段
与 C API 交互(
pthread_create之类)
方案 2:面向对象 + 统一基类(“OO Style”)
struct TaskBase { // 统一接口virtual ~TaskBase() = default;virtual void run() = 0;
};struct ClientTask : TaskBase {int fd;explicit ClientTask(int f) : fd(f) {}void run() override { handle_client(fd); }
};// 子进程入口永远只有这一句
void worker_main(TaskBase* task) {task->run();delete task;
}// 父进程
int client_fd = accept(listenfd, ...);
TaskBase* job = new ClientTask(client_fd);
pid_t pid = fork();
if (pid == 0) { worker_main(job); exit(0); }特点
✅ 类型安全,编译器帮你检查
✅ 加新任务只需写新派生类,入口函数不动(开闭原则)
❌ 一次虚函数调用 + 一次 new/delete,有极小额外开销
适用
业务逻辑经常扩展
团队里 C++ 比重高,怕
void*埋雷
方案 3:模板 + 泛型回调(“Modern C++ Style”)
template <typename F>
void worker_main(F f) { // 编译期生成专属入口f(); // 直接调 lambda
}// 父进程
int client_fd = accept(listenfd, ...);
auto job = [fd = client_fd]() { handle_client(fd); };
pid_t pid = fork();
if (pid == 0) { worker_main(std::move(job)); exit(0); }特点
✅ 零虚函数、零动态分配,效率 = 手写函数
✅ 捕获列表想带几个参数就带几个,类型自动推导
❌ 每个 lambda 都会产生一份新二进制入口,体积略增
❌ 必须 C++14 及以上,纯 C 场景玩不了
适用
性能敏感(高频 fork 短任务)
喜欢“头文件里就搞定”的轻量级风格
重点讲解一下第三种方法
🌟 整体设计思路
想象你要开两家店:
- 一家是"日志记录店"(
CreatelogServer) - 一家是"客户端服务店"(
CreateClientServer)
你作为老板(main函数)不能亲力亲为,所以你要:
- 招两个店长(
CProcess对象) - 给每个店长明确任务(
SetEntryFunction) - 让店长自己去开店(
CreateSubProcess) - 你继续做老板的其他事(main函数继续执行)
🔍 代码逐层拆解
1️⃣ 第一层:通用任务接口(CFunctionBase)
class CFunctionBase {
public:virtual ~CFunctionBase(){}virtual int operator()() = 0; // 纯虚函数
};👉 这是一个"任务合同模板"。规定所有任务必须:
- 可以像函数一样被调用(
operator()) - 有标准的结束方式(虚析构函数)
- 关键点:
= 0表示"这只是合同,具体内容由子类填写"
也是为了防止子类污染;
2️⃣ 第二层:具体任务包装(CFunction 模板类)
template<typename FUNC, typename... ARGS>
class CFunction : public CFunctionBase { // 必须继承基类!
private:std::function<int()> m_task; // 用标准库封装public:CFunction(FUNC func, ARGS... args) {// 用bind把函数和参数打包成可调用对象m_task = std::bind(func, args...);}int operator()() override {return m_task(); // 执行打包好的任务}
};👉 这是一个"任务包装盒",可以:
- 装入任意函数(比如
CreatelogServer) - 带上任意参数(比如
&proclog) - 封装成标准格式(符合
CFunctionBase合同)
3️⃣ 第三层:进程管家(CProcess 类)
class CProcess {
private:CFunctionBase* m_func; // 指向任务的指针,必须用基类,子类会被污染pid_t m_pid; // 进程IDpublic:// 设置任务template<typename FUNC, typename... ARGS>int SetEntryFunction(FUNC func, ARGS... args) {m_func = new CFunction<FUNC, ARGS...>(func, args...);return 0;}// 创建子进程int CreateSubProcess() {pid_t pid = fork(); // 关键!创建子进程if (pid == 0) {// 子进程:执行任务return (*m_func)();}// 父进程:记录子进程IDm_pid = pid;return 0;}
};👉 这是"店长角色",负责:
- 接收任务:
SetEntryFunction把函数打包成标准任务 - 分身术:
fork()创建子进程(一分为二,两个相同进程) - 分工:
- 子进程:执行任务(
(*m_func)()) - 父进程:记住子进程ID(方便后续管理)
- 子进程:执行任务(
💡 关键知识点:
fork()调用一次,返回两次:
- 父进程得到子进程ID(>0)
- 子进程得到0
- 这就是代码中
if (pid == 0)判断的原理
4️⃣ 第四层:具体业务(两个服务器函数)
int CreatelogServer(CProcess* proc) {// 实际应该写日志服务代码return 0;
}int CreateClientServer(CProcess* proc) {// 实际应该写客户端服务代码return 0;
}👉 这是两个"分店的具体业务手册":
CreatelogServer:专做日志记录CreateClientServer:专处理客户端请求- 参数
CProcess* proc理论上可以用于进程控制(但示例中未使用)
⚠️ 注意:这两个函数在示例中是空的(返回0),实际项目中会填充具体逻辑。
5️⃣ 最终组装(main 函数)
int main() {// 1. 创建两个"店长"CProcess proclog, procclients;// 2. 分配任务proclog.SetEntryFunction(CreatelogServer, &proclog);procclients.SetEntryFunction(CreateClientServer, &procclients);// 3. 让店长去开店(创建子进程)proclog.CreateSubProcess();procclients.CreateSubProcess();// 4. 老板继续自己的事...return 0;
}执行流程:
- 创建两个
CProcess对象(两个店长) - 为每个店长分配任务:
proclog负责运行CreatelogServerprocclients负责运行CreateClientServer
- 调用
CreateSubProcess()时发生"分身":- 第一次 fork:创建日志服务子进程
- 第二次 fork:创建客户端服务子进程
- 最终系统中有3个进程:
- 1个父进程(main函数,很快结束)
- 2个子进程(分别运行两个服务器)
