分类 默认分类 下的文章

当OpenWrt设备连接的PC能正常上网,但OpenWrt自身命令行(如通过SSH登录的系统终端)无法上网时,通常是OpenWrt系统自身的网络配置、DNS或防火墙规则出现了问题。以下是具体的排查思路和解决步骤:

一、先确认问题现象

在OpenWrt命令行中执行以下命令,判断具体故障点:

# 测试能否ping通外网IP(如百度DNS:180.76.76.76)
ping -c 3 180.76.76.76

# 测试能否解析域名(如ping百度)
ping -c 3 www.baidu.com
  • 若IP能ping通,但域名不能:问题出在 DNS配置
  • 若IP和域名都不能ping通:问题出在 网络接口配置、路由表或防火墙

二、针对性排查与解决

1. 排查DNS配置(域名无法解析)

OpenWrt命令行依赖DNS服务器解析域名,若DNS配置错误,会导致“能访问IP但不能访问域名”。

(1)查看当前DNS配置
# 查看系统DNS配置(临时生效,重启可能失效)
cat /etc/resolv.conf

# 查看OpenWrt持久化DNS配置(由dhcp服务管理)
cat /etc/config/dhcp
  • 正常情况下,/etc/resolv.conf 应包含有效的DNS服务器(如 nameserver 114.114.114.114 或运营商提供的DNS)。
  • 若文件为空或DNS服务器不可用(如 nameserver 127.0.0.1 但本地DNS服务未运行),则需要手动配置。
(2)临时修复DNS(立即生效)
# 手动添加公共DNS(如114DNS或阿里云DNS)
echo "nameserver 114.114.114.114" >> /etc/resolv.conf
echo "nameserver 223.5.5.5" >> /etc/resolv.conf
(3)持久化配置DNS(重启不失效)

OpenWrt的DNS通常由 dnsmasq 服务管理,需在配置文件中指定:

# 编辑dhcp配置
vi /etc/config/dhcp

# 在config dnsmasq部分添加或修改以下内容(指定上游DNS)
list server '114.114.114.114'
list server '223.5.5.5'

# 重启dnsmasq服务生效
/etc/init.d/dnsmasq restart

2. 排查网络接口与路由(IP无法ping通)

若IP也无法ping通,说明OpenWrt系统自身未正确连接外网,需检查 WAN接口配置路由表

(1)检查WAN接口是否获取到IP

OpenWrt的外网通常通过 wan 接口连接(可能是eth0.2、pppoe-wan等,根据实际配置而定):

# 查看所有接口状态(注意wan接口的IP、网关)
ip addr show
ip route show  # 查看路由表,重点看默认路由(default via ...)
  • 正常情况下,wan 接口应分配到IP(如 192.168.100.xxx 或公网IP),且路由表中有 默认路由(如 default via 192.168.100.1 dev wan)。
  • wan 接口无IP:可能是DHCP获取失败(如上级路由器未分配IP)或静态IP配置错误。
(2)修复WAN接口配置
  • 若WAN使用DHCP(自动获取IP):

    # 重启wan接口的DHCP客户端
    ifup wan
    
    # 查看DHCP获取日志,确认是否成功
    logread | grep -i dhcp
    • 若日志显示“无法获取IP”,检查上级设备(光猫、主路由器)是否正常,或网线是否插对(WAN口需连接上级设备的LAN口)。
  • 若WAN使用静态IP

    # 编辑网络配置
    vi /etc/config/network
    
    # 找到config interface 'wan'部分,确保配置正确:
    config interface 'wan'
        option ifname 'eth0.2'  # 接口名称,根据实际修改
        option proto 'static'
        option ipaddr '192.168.100.2'  # 与上级同网段的IP
        option netmask '255.255.255.0'
        option gateway '192.168.100.1'  # 上级设备网关
        option dns '114.114.114.114'    # 手动指定DNS
    
    # 重启网络生效
    /etc/init.d/network restart
(3)手动添加默认路由(临时应急)

若路由表中无默认路由,可临时手动添加(重启后失效,需持久化到 /etc/config/network):

# 假设网关为192.168.100.1,wan接口为eth0.2
ip route add default via 192.168.100.1 dev eth0.2

3. 排查防火墙规则(阻止系统出站流量)

OpenWrt的防火墙默认允许内网设备访问外网,但可能误配置了阻止系统自身(即路由器系统)访问外网的规则。

(1)查看防火墙配置
# 查看防火墙规则(重点看output链是否允许出站)
iptables -L OUTPUT
iptables -L zone_wan_output  # WAN区域的出站规则
  • 正常情况下,OUTPUT 链和 zone_wan_output 链应允许 ACCEPT 状态(即允许系统自身向WAN发送流量)。
  • 若规则为 DROPREJECT,则会阻止系统上网。
(2)临时允许WAN出站流量
# 允许系统向WAN发送所有流量(临时生效)
iptables -A zone_wan_output -j ACCEPT
iptables -A OUTPUT -j ACCEPT
(3)持久化防火墙配置
# 编辑防火墙配置
vi /etc/config/firewall

# 确保wan区域的output策略为ACCEPT
config zone
    option name 'wan'
    option input 'REJECT'
    option output 'ACCEPT'  # 允许出站
    option forward 'REJECT'
    option masq '1'
    option mtu_fix '1'

# 重启防火墙生效
/etc/init.d/firewall restart

4. 检查网络服务是否正常

部分核心服务(如 networkfirewall)未运行也可能导致问题:

# 检查关键服务状态
/etc/init.d/network status
/etc/init.d/firewall status
/etc/init.d/dnsmasq status

# 若服务未运行,手动启动
/etc/init.d/network start
/etc/init.d/firewall start
/etc/init.d/dnsmasq start

三、总结排查流程

  1. ping IPping 域名 区分是DNS问题还是网络连接问题。
  2. DNS问题:检查 /etc/resolv.confdnsmasq 配置,添加公共DNS。
  3. 网络连接问题:检查WAN接口IP、路由表默认路由,修复接口配置。
  4. 防火墙问题:确保WAN区域允许出站流量,避免规则阻止系统自身上网。

通过以上步骤,基本可以解决OpenWrt命令行无法上网的问题。若排查后仍有问题,可通过 logread 查看系统日志,进一步定位错误原因。

推荐阅读:https://zh.javascript.info/import-export

export导出
我们可以通过在声明之前放置 export 来标记任意声明为导出,无论声明的是变量,函数还是类都可以。

例如,这里的所有导出均有效:

// 导出数组
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

// 导出 const 声明的变量
export const MODULES_BECAME_STANDARD_YEAR = 2015;

// 导出类
export class User {
  constructor(name) {
    this.name = name;
  }
}

导出与声明分开
另外,我们还可以将 export 分开放置。

下面的例子中,我们先声明函数,然后再导出它们:

function sayHi(user) {
  alert(`Hello, ${user}!`);
}

function sayBye(user) {
  alert(`Bye, ${user}!`);
}

export {sayHi, sayBye}; // 导出变量列表

Import *
通常,我们把要导入的东西列在花括号 import {...} 中,就像这样:

import {sayHi, sayBye} from './say.js';

sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!

如果有很多要导入的内容,我们可以使用 import * as 将所有内容导入为一个对象

import * as say from './say.js';

say.sayHi('John');
say.sayBye('John');

Import “as” 和 Export “as”

我们也可以使用 as 让导入具有不同的名字。

import {sayHi as hi, sayBye as bye} from './say.js';

hi('John'); // Hello, John!
bye('John'); // Bye, John!
export {sayHi as hi, sayBye as bye};

现在 hi 和 bye 是在外面使用时的正式名称

Export default 导出的是一个模块
在实际中,主要有两种模块。

包含库或函数包的模块,像上面的 say.js
声明单个实体的模块,例如模块 user.js 仅导出 class User。
大部分情况下,开发者倾向于使用第二种方式,以便每个“东西”都存在于它自己的模块中。

当然,这需要大量文件,因为每个东西都需要自己的模块,但这根本不是问题。实际上,如果文件具有良好的命名,并且文件夹结构得当,那么代码导航(navigation)会变得更容易。

模块提供了一个特殊的默认导出 export default 语法,以使“一个模块只做一件事”的方式看起来更好。

将 export default 放在要导出的实体前:

export default class User { // 只需要添加 "default" 即可
  constructor(name) {
    this.name = name;
  }
}

每个文件应该只有一个 export default:

……然后将其导入而不需要花括号:

import User from './user.js'; // 不需要花括号 {User},只需要写成 User 即可

new User('John');

不用花括号的导入看起来很酷。刚开始使用模块时,一个常见的错误就是忘记写花括号。所以,请记住,import 命名的导出时需要花括号,而 import 默认的导出时不需要花括号。

命名导出
由于每个文件最多只能有一个默认的导出,因此导出的实体可能没有名称。

例如,下面这些都是完全有效的默认的导出:

export default class { // 没有类名
  constructor() { ... }
}
export default function(user) { // 没有函数名
  alert(`Hello, ${user}!`);
}
// 导出单个值,而不使用变量
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

不指定名称是可以的,因为每个文件只有一个 export default,因此不带花括号的 import 知道要导入的内容是什么。

如果没有 default,这样的导出将会出错:

export class { // Error!(非默认的导出需要名称)
  constructor() {}
}

“default” 名称
在某些情况下,default 关键词被用于引用默认的导出。

例如,要将函数与其定义分开导出:

function sayHi(user) {
  alert(`Hello, ${user}!`);
}

// 就像我们在函数之前添加了 "export default" 一样
export {sayHi as default};

或者,另一种情况,假设模块 user.js 导出了一个主要的默认的导出和一些命名的导出(这种情况很少见,但确实会发生):

export default class User {
  constructor(name) {
    this.name = name;
  }
}

export function sayHi(user) {
  alert(`Hello, ${user}!`);
}

这是导入默认的导出以及命名的导出的方法:

import {default as User, sayHi} from './user.js';

new User('John');

如果我们将所有东西 * 作为一个对象导入,那么 default 属性正是默认的导出:

import * as user from './user.js';

let User = user.default; // 默认的导出
new User('John');

命名的导出会强制我们使用正确的名称进行导入:

import {User} from './user.js';
// 导入 {MyUser} 不起作用,导入名字必须为 {User}

……对于默认的导出,我们总是在导入时选择名称:

import User from './user.js'; // 有效
import MyUser from './user.js'; // 也有效
// 使用任何名称导入都没有问题

重新导出
“重新导出(Re-export)”语法 export ... from ... 允许导入内容,并立即将其导出(可能是用的是其他的名字),就像这样:

export {sayHi} from './say.js'; // 重新导出 sayHi

export {default as User} from './user.js'; // 重新导出 default

重新导出默认导出
重新导出时,默认导出需要单独处理。

假设我们有一个 user.js 脚本,其中写了 export default class User,并且我们想重新导出类 User:

export default class User {
  // ...
}

我们可能会遇到两个问题:

  • export User from './user.js' 无效。这会导致一个语法错误。

要重新导出默认导出,我们必须明确写出 export {default as User},就像上面的例子中那样。

  • export * from './user.js' 重新导出只导出了命名的导出,但是忽略了默认的导出。

如果我们想将命名的导出和默认的导出都重新导出,那么需要两条语句:

export * from './user.js'; // 重新导出命名的导出
export {default} from './user.js'; // 重新导出默认的导出

动态导入
下面这样的 import 行不通:

import ... from getModuleName(); // Error, only from "string" is allowed

if(...) {
  import ...; // Error, not allowed!
}

{
  import ...; // Error, we can't put import in any block
}

import() 表达式

let modulePath = prompt("Which module to load?");

import(modulePath)
  .then(obj => <module object>)
  .catch(err => <loading error, e.g. if no such module>)

//或者导入 say 中的两个函数
let {hi, bye} = await import('./say.js');

hi();
bye();

//或者  默认的导出
let obj = await import('./say.js');
let say = obj.default;
// or, in one line: let {default: say} = await import('./say.js');

say();

尽管 import() 看起来像一个函数调用,但它只是一种特殊语法,只是恰好使用了括号(类似于 super())。
因此,我们不能将 import 复制到一个变量中,或者对其使用 call/apply。因为它不是一个函数。

PCNTL 扩展:PHP 进程控制与信号处理的核心工具

一、PCNTL 扩展概述

PCNTL(PHP Control Process)是 PHP 的一个扩展,主要用于进程控制信号处理,仅在 UNIX/Linux 系统(Windows 不完全支持)的 CLI 模式下有效。它提供了与操作系统底层进程管理相关的功能,常用于开发守护进程、任务调度、异步处理等场景。

二、核心功能与应用场景

功能分类具体能力典型场景
进程创建pcntl_fork() 创建子进程,实现多进程并发处理高并发任务处理、分布式计算
进程等待与回收pcntl_wait()/pcntl_waitpid() 等待子进程结束并回收资源,避免僵尸进程守护进程管理、任务调度系统
信号处理pcntl_signal() 注册信号处理器,响应系统信号(如 SIGINT、SIGTERM 等)程序优雅退出、异步事件处理
进程间通信配合 sysvmsg/sysvsem 等扩展实现进程间数据交换微服务通信、分布式任务队列
进程状态控制pcntl_exec() 替换当前进程的内存空间,执行外部程序动态加载程序、命令行工具集成

三、安装与启用

  1. 编译时安装(适用于源码安装 PHP):

    ./configure --enable-pcntl [--with-pcntl=DIR]
    make && make install
  2. 包管理器安装(适用于 Linux 系统):

    • Debian/Ubuntu:sudo apt-get install php-pcntl
    • CentOS/RHEL:sudo yum install php-pcntl(或通过 Remi 源)
  3. 确认启用
    执行 php -m | grep pcntl,若输出 pcntl 则表示扩展已启用。

四、核心函数与示例

1. 进程创建与回收(pcntl_fork
<?php
// fork_demo.php - 多进程示例
echo "主进程启动 (PID: " . posix_getpid() . ")\n";

$pid = pcntl_fork();

if ($pid === -1) {
    die("fork 失败: " . pcntl_strerror(pcntl_get_last_error()));
} elseif ($pid === 0) {
    // 子进程逻辑
    echo "子进程启动 (PID: " . posix_getpid() . ")\n";
    echo "子进程工作中...\n";
    sleep(2);
    echo "子进程完成任务,退出\n";
    exit(0);
} else {
    // 父进程逻辑
    echo "父进程继续执行,子进程PID: $pid\n";
    // 等待子进程结束并回收资源
    $status = 0;
    $childPid = pcntl_wait($status);
    echo "子进程 $childPid 已退出,状态码: " . pcntl_wexitstatus($status) . "\n";
}

执行结果

主进程启动 (PID: 1000)
父进程继续执行,子进程PID: 1001
子进程启动 (PID: 1001)
子进程工作中...
子进程完成任务,退出
子进程 1001 已退出,状态码: 0
2. 信号处理(pcntl_signal
<?php
// signal_demo.php - 信号处理示例(需 CLI 运行)
function signalHandler($signal) {
    switch ($signal) {
        case SIGINT:  // Ctrl+C
            echo "\n接收到 SIGINT,准备退出...\n";
            exit(0);
        case SIGUSR1: // 自定义信号1
            echo "\n接收到 SIGUSR1,执行日志刷新...\n";
            // 此处可添加日志刷新逻辑
            break;
        default:
            echo "\n接收到未知信号: $signal\n";
    }
}

// 注册信号处理器
pcntl_signal(SIGINT, 'signalHandler');
pcntl_signal(SIGUSR1, 'signalHandler');

echo "程序运行中 (PID: " . posix_getpid() . ")\n";
echo "按 Ctrl+C 退出,或使用 kill -SIGUSR1 PID 发送信号\n";

// 保持程序运行
while (true) {
    pcntl_signal_dispatch(); // 处理信号
    sleep(1);
    echo ".";
}

测试方法

  1. 运行程序后,按 Ctrl+C 触发 SIGINT,程序优雅退出。
  2. 另开终端执行 kill -SIGUSR1 <PID>,触发自定义信号处理。
3. 进程替换(pcntl_exec
<?php
// exec_demo.php - 进程替换示例
echo "原进程 (PID: " . posix_getpid() . ") 执行中...\n";

$args = [
    'ls',           // 程序路径
    '-la',          // 参数1:长列表格式
    '/etc'          // 参数2:目标目录
];

// 替换当前进程为 ls 命令,原进程不再继续执行
pcntl_exec('/bin/ls', $args);

// 以下代码不会执行
echo "此句不会输出,进程已被替换\n";

执行结果
直接输出 /etc 目录的详细列表,原 PHP 进程被 ls 命令完全替换。

五、注意事项与限制

  1. 平台限制:仅 UNIX/Linux 系统支持完整功能,Windows 仅部分函数可用(如 pcntl_signal 有限支持)。
  2. Web 环境限制:Web 服务器(如 Apache/Nginx)中使用 pcntl_fork 可能导致进程混乱,仅适用于 CLI 模式。
  3. 资源管理:必须显式调用 pcntl_wait 回收子进程,避免僵尸进程堆积。
  4. 信号安全:信号处理函数中应避免复杂操作,确保可重入性(如不操作全局变量、数据库连接等)。
  5. 扩展依赖:部分功能(如进程间通信)需配合 sysvmsgsysvsem 等扩展使用。

六、实际应用场景

  • 守护进程开发:如消息队列消费者、日志收集器、定时任务调度器。
  • 高并发任务处理:通过多进程拆分计算密集型任务(如图片处理、数据渲染)。
  • 微服务架构:子进程处理请求,父进程监控服务状态,实现自动重启。
  • 信号驱动编程:异步响应系统事件(如文件变更、网络请求)。

通过 PCNTL 扩展,PHP 得以突破单进程限制,实现更接近系统底层的进程控制能力,是开发高性能后端服务的重要工具。

在PHP里,declare结构可对代码块的执行行为加以控制。它能够设定指令,对代码的运行环境或者解析方式产生影响。下面为你详细介绍其作用、用法以及相关注意事项。

一、核心作用与用法

declare主要用于以下方面:

  1. 对代码块的执行指令(如strict_typesticks)进行设定。
  2. 规定代码块的时间限制(借助 ticks和信号处理)。
  3. 启用严格类型检查(针对函数参数和返回值)。

基础语法

declare (指令1 [, 指令2 [, ...]]) {
    // 代码块
}

// 或者单行形式(仅对下一条语句生效)
declare(指令); 语句;

二、常见指令及示例

1. strict_types=1(严格类型模式)

该指令会让函数的参数和返回值类型声明采用严格类型检查。

<?php
// 在文件顶部启用严格类型(影响整个文件)
declare(strict_types=1);

function sum(int $a, int $b): int {
    return $a + $b;
}

// 下面这行会报错,因为5.5是浮点数,与int类型不匹配
echo sum(5, 5.5);

关键点

  • 必须在文件的最开始位置声明(在任何非PHP标签和空白之后)。
  • 严格检查类型,不允许隐式类型转换(例如,不能把浮点数当作整数传递)。

2. ticks=1(时钟周期)

ticks能在执行指定数量的低级语句后,触发注册的回调函数。

<?php
// 每执行1条低级语句就触发一次register_tick_function
declare(ticks=1);

// 注册回调函数
register_tick_function(function() {
    echo "执行了一个tick\n";
});

// 循环中的每条语句都会触发tick
for ($i = 0; $i < 2; $i++) {
    echo "循环迭代\n";
}

输出结果

执行了一个tick
循环迭代
执行了一个tick
执行了一个tick
循环迭代
执行了一个tick
执行了一个tick

应用场景

  • 实现代码覆盖率分析。
  • 进行性能监控。
  • 处理异步信号(在CLI模式下)。

3. encoding='编码格式'(字符编码)

此指令用于声明文件的编码格式(不过在实际中很少使用)。

<?php
// 声明文件使用UTF-8编码
declare(encoding='UTF-8');

三、作用域规则

  • 文件级作用域:如果declare放在文件的全局作用域,那么它会对整个文件生效。

    <?php
    declare(strict_types=1); // 对整个文件生效
    
    function test() { /* ... */ }
  • 代码块作用域:若declare包含在代码块中,就只对该代码块有效。

    <?php
    // 非严格模式
    function add($a, $b) {
      return $a + $b;
    }
    
    // 严格模式代码块
    declare(strict_types=1) {
      function multiply(int $a, int $b): int {
          return $a * $b;
      }
    }
  • 单行声明:单行的declare仅对接下来的一条语句起作用。

    <?php
    declare(ticks=1); echo "这条语句会触发tick";
    echo "这条语句不会触发tick";

四、与set_time_limit()的对比

ticks可用于实现更精细的超时控制:

<?php
declare(ticks=1);

// 超时处理函数
function handle_timeout() {
    throw new Exception("执行超时");
}

// 注册信号处理
pcntl_signal(SIGALRM, 'handle_timeout');

// 设置3秒后触发ALARM信号(仅在CLI模式下有效)
pcntl_alarm(3);

// 模拟耗时操作
while (true) {
    usleep(100000);
}

五、注意事项

  1. 版本兼容性

    • strict_types从PHP 7.0开始支持。
    • ticks在PHP 5.3之前存在性能问题,之后的版本有所优化。
  2. 性能影响

    • 过多使用ticks会使性能下降,因为每条低级语句都会触发回调。
  3. 信号处理限制

    • 信号处理(如pcntl_alarm)仅在CLI模式下有效,在Web环境中无效。
  4. 编码声明

    • encoding指令主要用于文档说明,PHP默认使用UTF-8,通常无需特别声明。

六、总结

指令用途示例
strict_types=1启用严格类型检查declare(strict_types=1);
ticks=N每执行N条低级语句触发回调declare(ticks=1); register_tick_function(...)
encoding='UTF-8'声明文件编码格式declare(encoding='UTF-8');

合理运用declare能够提升代码的健壮性、安全性和可维护性,特别是在需要严格类型控制或者复杂执行环境管理的场景中。

实现代码覆盖率分析。
进行性能监控。
处理异步信号(在 CLI 模式下)。分别举一个例子:

一、实现代码覆盖率分析

示例:统计函数执行覆盖率

<?php
// coverage.php - 使用 ticks 实现简单的代码覆盖率分析
declare(ticks=1);

// 存储已执行的行号
$coveredLines = [];

// 注册 tick 函数,每次执行低级语句时记录行号
register_tick_function(function() use (&$coveredLines) {
    $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
    if (!empty($trace[0]['file']) && !empty($trace[0]['line'])) {
        $file = $trace[0]['file'];
        $line = $trace[0]['line'];
        if (strpos($file, __FILE__) !== false) { // 只记录当前文件
            $coveredLines[$line] = true;
        }
    }
});

// 示例函数
function calculateSum($a, $b) {
    if ($a > 0) {
        return $a + $b;
    } else {
        return $b;
    }
}

// 测试用例
$result = calculateSum(5, 3);

// 输出覆盖率报告
echo "代码覆盖率分析结果:\n";
$source = file(__FILE__);
foreach ($source as $lineNum => $line) {
    $realLineNum = $lineNum + 1;
    $status = isset($coveredLines[$realLineNum]) ? "[✓]" : "[ ]";
    echo "$status $realLineNum: $line";
}

执行结果示例

代码覆盖率分析结果:
[✓] 1: <?php
[✓] 2: // coverage.php - 使用 ticks 实现简单的代码覆盖率分析
[✓] 3: declare(ticks=1);
...
[✓] 11:         $coveredLines[$line] = true;
[ ] 12:         }
...
[✓] 19: function calculateSum($a, $b) {
[✓] 20:     if ($a > 0) {
[✓] 21:         return $a + $b;
[ ] 23:     } else {
[ ] 24:         return $b;
...

二、进行性能监控

示例:使用 ticks 监控函数执行时间

<?php
// performance.php - 函数级性能监控
declare(ticks=1);

// 存储函数执行时间(微秒)
$functionTimers = [];
$currentFunction = null;

// 注册 tick 函数
register_tick_function(function() use (&$functionTimers, &$currentFunction) {
    if ($currentFunction) {
        $functionTimers[$currentFunction] += 1;
    }
});

// 函数调用前记录开始时间
function startTimer($functionName) {
    global $currentFunction;
    $currentFunction = $functionName;
    $GLOBALS['functionTimers'][$functionName] = 0;
}

// 函数调用后输出耗时
function endTimer($functionName) {
    global $currentFunction;
    $currentFunction = null;
    echo "函数 {$functionName} 执行了 {$GLOBALS['functionTimers'][$functionName]} 个 tick\n";
}

// 示例函数
function processData() {
    startTimer(__FUNCTION__);
    for ($i = 0; $i < 1000; $i++) {
        // 模拟耗时操作
        sqrt($i);
    }
    endTimer(__FUNCTION__);
}

function fetchData() {
    startTimer(__FUNCTION__);
    usleep(500000); // 休眠500毫秒
    endTimer(__FUNCTION__);
}

// 执行测试
processData();
fetchData();

执行结果示例

函数 processData 执行了 3005 个 tick
函数 fetchData 执行了 3 个 tick

三、处理异步信号(CLI 模式)

示例:使用 PCNTL 处理 SIGINT(Ctrl+C)和 SIGTERM

<?php
// signal.php - 异步信号处理示例(需在 CLI 模式运行)
declare(ticks=1);

// 注册信号处理函数
function signalHandler($signal) {
    switch ($signal) {
        case SIGINT:  // Ctrl+C
            echo "\n接收到 SIGINT 信号,优雅退出...\n";
            exit(0);
        case SIGTERM: // kill 命令
            echo "\n接收到 SIGTERM 信号,清理资源...\n";
            // 这里可以添加资源清理代码
            exit(0);
        case SIGALRM: // 定时器信号
            echo "\n定时器触发,执行定时任务...\n";
            pcntl_alarm(2); // 重新设置2秒后触发
            break;
    }
}

// 安装信号处理器
pcntl_signal(SIGINT, 'signalHandler');
pcntl_signal(SIGTERM, 'signalHandler');
pcntl_signal(SIGALRM, 'signalHandler');

// 设置定时器(2秒后触发 SIGALRM)
pcntl_alarm(2);

echo "程序运行中(PID: " . getmypid() . "),按 Ctrl+C 退出...\n";

// 主循环
while (true) {
    // 处理待处理的信号
    pcntl_signal_dispatch();
    sleep(1);
    echo ".";
}

执行结果示例

程序运行中(PID: 12345),按 Ctrl+C 退出...
..
定时器触发,执行定时任务...
..
定时器触发,执行定时任务...
^C
接收到 SIGINT 信号,优雅退出...

注意事项

  1. 信号处理仅在 CLI 模式有效,Web 环境中会被忽略
  2. 需要启用 PCNTL 扩展(编译 PHP 时需包含 --enable-pcntl)
  3. Windows 系统不完全支持 POSIX 信号,部分信号可能无法正常工作