
在使用 php 进行串口通信,特别是与外部硬件(如树莓派上的按钮输入)交互时,一个常见且关键的需求是等待特定数据的到来。然而,如果外部设备长时间没有响应,或者数据格式不符合预期,程序可能会无限期地等待串口数据,从而导致整个进程阻塞,甚至因为达到 php 的最大执行时间而终止。
原始代码尝试通过一个 while (true) 循环结合 time() 函数来手动实现超时机制:
// ... 串口初始化代码 ...
$timeoutStart = time();
$timeout = $timeoutStart + 15; // 15 秒超时
$aborted = false;
while (true) {
$data2 = $this->serialPort->read(); // 关键的阻塞点
if (Str::contains($data2, "Whatever I want to check for")) {
// 处理数据并跳出循环
break;
}
// 检查是否超时
if (time() >= $timeout) {
$this->serialPort->write("C,STOP
"); // 发送停止命令
$aborted = true;
$this->alert("vending sequence stopped");
break; // 超时跳出循环
}
}尽管代码中包含了超时检查逻辑,但实际运行中发现,即使超时时间已到,循环也可能“突然停止”或根本不执行超时逻辑。这通常是因为 $this->serialPort->read() 方法本身是一个阻塞调用,它会在没有数据到来或未检测到分隔符时无限期地等待,从而阻止了 time() 检查的执行。当 read() 方法被阻塞时,PHP 脚本会暂停,直到数据到达或达到 PHP 的 max_execution_time 限制而被系统强制终止。
要解决这个问题,我们需要理解 lepiaf\SerialPort 库的内部工作机制。通过查看其源代码,特别是 SerialPort.php 文件中的 read 方法,我们可以发现:
public function read()
{
$this->ensureDeviceOpen();
$chars = [];
do {
$char = fread($this->fd, 1); // 从文件描述符读取一个字符
// ... 其他逻辑 ...
} while ($char !== $this->getParser()->getSeparator()); // 循环直到找到分隔符
return $this->getParser()->parse($chars);
}尽管 lepiaf\SerialPort 库可能将串口流设置为非阻塞模式,但其 read 方法内部的 do...while 循环却是一个阻塞点。它会不断地从串口读取单个字符,直到遇到配置的分隔符为止。这意味着,如果串口长时间没有数据,或者没有发送分隔符,这个 read 方法将永远不会返回,从而导致外部的超时逻辑无法执行。
立即学习“PHP免费学习笔记(深入)”;
最直接有效的解决方案是修改 lepiafSerialPort 库的 read 方法,使其能够接受一个超时参数,并在指定时间内未接收到分隔符时返回。
请编辑 vendor/lepiaf/serialport/src/lepiaf/SerialPort/SerialPort.php 文件,找到 read 方法(通常在第 107 行左右),并将其修改为以下形式:
public function read($maxElapsed = 'infinite')
{
$this->ensureDeviceOpen();
$chars = [];
// 计算超时时间点,使用 microtime(true) 获取微秒级时间戳
$timeout = $maxElapsed == 'infinite' ? 1.7976931348623E+308 : (microtime(true) + $maxElapsed);
do {
$char = fread($this->fd, 1); // 尝试读取一个字符
if ($char === '') { // 如果没有读取到字符(非阻塞模式下)
if (microtime(true) > $timeout) {
return false; // 超时,返回 false
}
usleep(100); // 短暂休眠,避免 CPU 占用过高
continue; // 继续循环等待
}
$chars[] = $char; // 读取到字符,添加到缓冲区
} while ($char !== $this->getParser()->getSeparator()); // 循环直到找到分隔符
return $this->getParser()->parse($chars); // 解析并返回数据
}修改说明:
修改库文件后,您就可以在自己的应用代码中调用带有超时参数的 read 方法,并根据其返回值进行相应的处理:
// ... 串口初始化代码 ...
// 尝试读取串口数据,设置 15 秒的超时时间
$data2 = $this->serialPort->read(15);
if ($data2 === false) {
// 发生了超时,可以执行相应的错误处理或回滚操作
$this->serialPort->write("C,STOP
"); // 发送停止命令
$aborted = true;
$this->alert("vending sequence stopped due to timeout");
// 这里可以进行 API 调用来回滚之前的操作
// 例如:$this->apiClient->revertSomeAction();
} elseif (Str::contains($data2, "Whatever I want to check for")) {
// 成功读取到数据,并且包含了期望的字符串
// 处理数据并继续流程
$this->alert("Received expected data: " . $data2);
} else {
// 成功读取到数据,但未包含期望的字符串
$this->alert("Received data, but not expected: " . $data2);
// 可以选择继续等待或采取其他措施
}通过这种方式,您的应用程序可以更健壮地处理串口通信,避免因外部设备无响应而导致的程序阻塞。
通过上述修改和实践,您可以在 PHP 应用中实现一个可靠的串口读取超时机制,有效提升系统的稳定性和用户体验。
以上就是PHP 串口通信读取超时机制:解决阻塞问题与实现方法的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号