PHP stream_socket_pair 怎么让父子进程双向传递消息
来源:17golang原创
时间:2026-11-05 13:47:16 482浏览 收藏
先用 stream_socket_pair 创建两个相连的本地socket流,再调用 pcntl_fork。父进程保留端点0、关闭端点1,子进程保留端点1、关闭端点0;两边保留的端点都能读写。再约定父进程先发请求、子进程读完后回复,就能完成一次双向通信。
官方地址:https://www.php.net/manual/en/function.stream-socket-pair.php
先确认是CLI父子进程通信
遇到“父进程发了消息,子进程没有反应”时,我会先看运行环境和协议约定,而不是先加入sleep。本文采用Unix类系统CLI、PCNTL和阻塞流的小消息场景;代码使用PHP 8字符串函数,不适合作为Windows父子进程方案直接照搬,也不应直接放进普通Web请求处理流程。
这里没有监听端口、接受连接或连接远端服务器。pair在fork之前创建,父子进程继承连接资源。若各自在fork之后创建一对socket,得到的是两套独立连接,不能据相同数组下标建立通信。
把两个端点分别交给父进程与子进程
两个端点是对等的,下标只用于本例分工,不是“读端”和“写端”。父端写出的数据由子端读取,子端写出的数据由父端读取。fork以后双方都会暂时持有两个端点,应立即关闭各自不用的那个。
关闭多余端点有两个用途:减少资源占用,让连接的结束语义更明确。仍有进程持有某个端点时,另一侧等待EOF可能与预期不同。不能只在PHP变量层面说“不使用”,而不实际关闭资源。

用换行协议完成一次请求和应答
流式socket承载字节,不为每次fwrite保存应用消息边界。下面用换行分隔,限制单条消息,并循环处理短写。示例只发送短文本,预期应答内容为 ack:task-42,这不是本文实际运行输出。
4096) {
throw new RuntimeException('消息格式不符合约定');
}
$wire = $message . "\n";
$offset = 0;
while ($offset getMessage() . PHP_EOL);
$exitCode = 1;
} finally {
fclose($child);
}
exit($exitCode); // 子进程不继续执行父进程分支。
}
fclose($pair[1]); // 父进程只保留端点0,同一端点可以读也可以写。
$parent = $pair[0];
$reply = null;
$failure = null;
try {
if (!stream_set_timeout($parent, 5)) {
throw new RuntimeException('设置读取超时失败');
}
sendLine($parent, 'task-42'); // 父进程先写,避免双方都等待读取。
$reply = receiveLine($parent);
} catch (Throwable $e) {
$failure = $e;
} finally {
fclose($parent);
// 关闭端点后回收子进程;信号中断时继续等待。
do {
$waited = pcntl_waitpid($pid, $status);
} while ($waited === -1 && pcntl_get_last_error() === PCNTL_EINTR);
}
if ($failure !== null) {
throw $failure;
}
if ($waited !== $pid || !pcntl_wifexited($status)
|| pcntl_wexitstatus($status) !== 0) {
throw new RuntimeException('子进程未正常结束');
}
echo $reply, PHP_EOL;
这里把数据写入、读到完整应答、子进程正常退出分别判断。写入成功只表示字节交给流,不表示对方已经处理;收到应答才体现这一轮协议完成。
示例的4096字节限制是教学选择。子进程在载荷前添加 ack:,所以接近长度上限的请求可能使应答超限并进入错误路径;业务设计应同时计算请求和应答的最大尺寸。任意二进制或多行消息更适合长度前缀协议,本例不扩展这部分。
卡住或读到半条消息时检查什么
| 现象 | 先检查 | 对应处理 |
|---|---|---|
| 双方都停在读取 | 是否双方都先fgets | 指定一方先发送 |
| 消息有内容却不返回 | 是否缺少换行 | 保证消息结束符完整 |
| 只传了部分内容 | fwrite返回的字节数 | 继续写剩余字节,0或false进入错误路径 |
| 读到片段却误认为成功 | 是否确实有完整结束符 | 拒绝不完整帧,不当正常消息使用 |
| 等待EOF没有结束 | 多余端点是否仍被持有 | 关闭每个进程不用的端点 |
五秒超时只用于帮助这个短消息示例识别读取异常,不是整个父子进程生命周期的硬截止时间。实际应用若子进程可能陷入业务计算,需要额外的生命周期管理;父进程的waitpid不会因为流的读取超时自动结束等待。
双方先写大量数据也可能因缓冲区容量而阻塞。这个请求应答示例没有实现大消息并发收发,也没有非阻塞事件循环;不能只把字符串放大就认为能保持同样行为。

把端点关闭与子进程回收一起收尾
子进程需要在自己的分支关闭端点并退出,父进程关闭端点后还要回收子进程。通信资源的关闭与进程状态回收属于不同工作:fclose不能替代waitpid,waitpid也不能替代消息是否完整的判断。
我会保留四个最小约定:端点归属固定、消息边界明确、读写顺序一致、异常后能够关闭和回收。它们能让短消息通信的行为更容易解释,比依赖任意休眠时间更可靠。
一定要两对socket才能双向通信吗?不用。一对相连的流式socket两侧都能读写。
收到EOF算一条成功消息吗?本例不算。只有带换行的完整消息才成功,EOF前的残缺内容进入错误路径。
可以先waitpid再读应答吗?不适合盲目这样做。子进程可能等待父进程读取才能完成写入,父进程却在等待它退出,形成互等。
参考:stream_socket_pair;pcntl_fork;fwrite;fgets;pcntl_waitpid。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
368 收藏
-
426 收藏
-
381 收藏
-
239 收藏
-
129 收藏
-
371 收藏
-
135 收藏
-
467 收藏
-
436 收藏
-
256 收藏
-
106 收藏
-
207 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习