登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

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端点的关系
图1:端点归属结构说明图。父进程保留0,子进程保留1,两个保留端点均可双向读写。

用换行协议完成一次请求和应答

流式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不会因为流的读取超时自动结束等待。

双方先写大量数据也可能因缓冲区容量而阻塞。这个请求应答示例没有实现大消息并发收发,也没有非阻塞事件循环;不能只把字符串放大就认为能保持同样行为。

行消息的发送读取和错误处理组成
图2:消息协议结构说明图。换行、长度约束、完整写入与完整读取共同定义一条消息,不表示执行记录。

把端点关闭与子进程回收一起收尾

子进程需要在自己的分支关闭端点并退出,父进程关闭端点后还要回收子进程。通信资源的关闭与进程状态回收属于不同工作:fclose不能替代waitpid,waitpid也不能替代消息是否完整的判断。

我会保留四个最小约定:端点归属固定、消息边界明确、读写顺序一致、异常后能够关闭和回收。它们能让短消息通信的行为更容易解释,比依赖任意休眠时间更可靠。

一定要两对socket才能双向通信吗?不用。一对相连的流式socket两侧都能读写。

收到EOF算一条成功消息吗?本例不算。只有带换行的完整消息才成功,EOF前的残缺内容进入错误路径。

可以先waitpid再读应答吗?不适合盲目这样做。子进程可能等待父进程读取才能完成写入,父进程却在等待它退出,形成互等。

参考:stream_socket_pair;pcntl_fork;fwrite;fgets;pcntl_waitpid。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>