PHP实现OPC UA客户端:开源库推荐与代码示例
PHP现在可以直接作为OPC UA客户端连接PLC、SCADA、传感器或其他OPC UA服务器,不一定要先经过Node.js、Python或Java中转。对于新的PHP项目,可以优先考虑纯PHP的 php-opcua/opcua-client,通过Composer安装后直接使用 opc.tcp:// 连接服务器,完成节点浏览、数据读取、写入、订阅和安全认证。对于对底层协议栈成熟度要求更高的项目,也可以让PHP通过独立服务调用open62541。
下面从库的选择开始,实际完成一次“连接服务器 → 找到节点 → 读取数据 → 写入数据 → 订阅变化 → 配置安全连接”的完整流程。
PHP连接OPC UA到底需要哪些东西
如果以前接触过Modbus,很容易把OPC UA理解成“连接IP和端口,然后读一个地址”。实际上OPC UA多了一层完整的信息模型。
PHP应用连接的不是一个简单寄存器,而是OPC UA服务器的地址空间。服务器内部可能存在设备、对象、变量、方法等不同节点,每个节点通过NodeId标识。例如:
ns=2;s=Temperature
ns=2;s=PLC/Speed
ns=3;i=1001
i=2259
其中 ns 表示命名空间索引,s 表示字符串型标识符,i 表示数字型标识符。
一次典型读取过程可以理解为:PHP先通过TCP连接OPC UA Endpoint,建立SecureChannel和Session,然后向服务器发送Read请求,请求指定NodeId当前的Value。
PHP有哪些OPC UA开源库可以选择
PHP的OPC UA生态不像C、C++、.NET和Java那样庞大,但已经出现可以直接处理OPC UA Binary协议的纯PHP实现。选择时不要只看“能否连接”,还要看安全策略、证书、订阅、断线恢复以及目标PHP版本。
| 方案 | 主要特点 | 适合场景 | 需要注意 |
|---|---|---|---|
php-opcua/opcua-client |
纯PHP,直接支持opc.tcp,包含Browse、Read、Write、订阅、历史读取和安全连接 | 希望PHP直接连接PLC或OPC UA服务器 | 要求PHP 8.2+,生产部署前应针对实际设备验证兼容性 |
techdock/opcua |
另一套现代PHP OPC UA客户端实现 | PHP 8.4+项目,希望评估不同纯PHP实现 | 项目仍处于较早版本阶段,应充分测试 |
| open62541 | C语言开源OPC UA协议栈,可实现客户端和服务器 | 工业网关、边缘服务、长期运行的独立通信进程 | 不是PHP库,通常需要通过独立服务、FFI或其他接口与PHP集成 |
php-opcua/opcua-client 当前项目文档说明其使用PHP 8.2+,运行时核心依赖为OpenSSL扩展,并直接实现OPC UA Binary协议、会话、安全通道、读写、浏览和订阅等功能。:contentReference[oaicite:0]{index=0}
techdock/opcua 在Packagist中要求PHP 8.4及Sockets扩展,也提供Binary Protocol、安全通道、Session、Browse、Read/Write和订阅等能力,可作为另一种PHP原生方案评估。:contentReference[oaicite:1]{index=1}
open62541则是C语言实现的开源OPC UA协议栈,采用MPL 2.0许可证,官方文档提供客户端和服务器能力。它并不能直接替代Composer里的PHP包,但如果系统需要长期保持大量设备连接,可以把open62541部署为独立采集服务,再把整理后的数据交给PHP业务系统。:contentReference[oaicite:2]{index=2}
安装php-opcua/opcua-client
下面以 php-opcua/opcua-client 为主进行演示。假设项目目录为:
/var/www/opcua-demo
进入项目目录,通过Composer安装:
cd /var/www/opcua-demo
composer require php-opcua/opcua-client
确认PHP已经启用OpenSSL:
php -m | grep -i openssl
示例输出:
openssl
如果没有输出,需要根据当前PHP安装方式启用OpenSSL扩展。这里不要直接照搬某个Linux发行版的包名,因为PHP可能来自系统软件仓库、Remi、宝塔、Docker或自行编译。
从安装到读取变量的完整操作顺序
实际接入PLC时,推荐不要一开始就写复杂业务逻辑,而是按固定顺序验证。先确认网络和Endpoint,再浏览节点,找到正确NodeId,最后进行Read和Write。
最简单的PHP OPC UA读取示例
假设OPC UA服务器地址为:
opc.tcp://192.168.1.100:4840
需要读取的变量NodeId为:
ns=2;s=Temperature
创建文件:
/var/www/opcua-demo/read.php
代码如下:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use PhpOpcua\Client\ClientBuilder;
$endpoint = 'opc.tcp://192.168.1.100:4840';
$nodeId = 'ns=2;s=Temperature';
$client = null;
try {
$client = ClientBuilder::create()
->connect($endpoint);
$dataValue = $client->read($nodeId);
echo "NodeId: {$nodeId}\n";
echo "Value: ";
var_dump($dataValue->getValue());
} catch (Throwable $e) {
fwrite(STDERR, 'OPC UA读取失败:' . $e->getMessage() . PHP_EOL);
exit(1);
} finally {
if ($client !== null) {
$client->disconnect();
}
}
在项目目录执行:
php read.php
如果服务器允许匿名连接,并且NodeId确实存在,就可以得到变量当前值。
例如温度变量类型为Double时,示例输出可能类似:
NodeId: ns=2;s=Temperature
Value: float(23.6)
这里的 23.6 只是示例结果,实际值由OPC UA服务器返回。
不知道NodeId时先浏览地址空间
实际接入设备时,最常见的问题往往不是PHP代码,而是不知道设备厂商到底把变量放在哪个NodeId下面。
OPC UA标准地址空间中的Objects目录可以使用 i=85 表示。可以先从这里浏览。
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use PhpOpcua\Client\ClientBuilder;
$client = ClientBuilder::create()
->connect('opc.tcp://192.168.1.100:4840');
try {
$references = $client->browse('i=85');
foreach ($references as $reference) {
echo $reference->displayName
. ' => '
. $reference->nodeId
. PHP_EOL;
}
} finally {
$client->disconnect();
}
示例输出可能类似:
Server => ns=0;i=2253
PLC1 => ns=2;i=1000
ProductionLine => ns=2;s=ProductionLine
设备的实际节点结构完全由服务器决定,因此不要把网上文章里的 ns=2;s=Temperature 直接当成自己的PLC地址。示例NodeId只是说明格式。
NodeId里的ns=2到底是什么意思
这里很容易踩坑。ns=2 不是固定代表“PLC变量”,它只是当前服务器NamespaceArray中的第2号命名空间索引。
例如某台设备可能使用:
ns=2;s=Motor.Speed
换一台服务器,同一个厂商模型也可能因为命名空间加载顺序不同而变成:
ns=3;s=Motor.Speed
所以在正式系统中,应尽量了解设备的Namespace URI与NodeId映射,不要盲目假定Namespace Index永远不变。
PHP写入OPC UA变量
读取正常以后,再测试写操作。假设服务器暴露一个设定值:
ns=2;s=Setpoint
并且该变量的数据类型为Double,可以显式指定类型:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use PhpOpcua\Client\ClientBuilder;
use PhpOpcua\Client\Types\BuiltinType;
$client = ClientBuilder::create()
->connect('opc.tcp://192.168.1.100:4840');
try {
$client->write(
'ns=2;s=Setpoint',
42.5,
BuiltinType::Double
);
$result = $client->read('ns=2;s=Setpoint');
echo '当前设定值:';
var_dump($result->getValue());
} finally {
$client->disconnect();
}
写入之后再读一次,是开发阶段比较直接的验证方法。但要注意,“读回42.5”并不一定意味着实际设备已经完成动作。某些PLC变量只是命令输入,真正执行结果还需要读取对应状态节点确认。
为什么有时能连接,却写入失败
Read成功而Write失败并不奇怪,常见原因包括:
- 节点本身只读;
- 匿名用户只有读取权限;
- 用户账号没有写入权限;
- 写入的数据类型与服务器变量类型不一致;
- PLC当前工作状态禁止修改参数;
- 服务器要求使用加密连接后才开放写操作。
因此不要看到“连接正常”就判断OPC UA配置完全正确。连接、Session认证、节点权限和变量数据类型属于不同层次的问题。
生产环境应配置安全连接
前面的代码直接调用 connect(),更适合受控测试网络。正式环境如果服务器支持安全Endpoint,应根据设备配置使用安全策略和消息安全模式。
php-opcua/opcua-client 文档提供了 Basic256Sha256 与 SignAndEncrypt 的连接方式,也支持客户端证书以及用户名密码认证。:contentReference[oaicite:3]{index=3}
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use PhpOpcua\Client\ClientBuilder;
use PhpOpcua\Client\Security\SecurityMode;
use PhpOpcua\Client\Security\SecurityPolicy;
$client = ClientBuilder::create()
->setSecurityPolicy(SecurityPolicy::Basic256Sha256)
->setSecurityMode(SecurityMode::SignAndEncrypt)
->setClientCertificate(
'/etc/opcua/client.pem',
'/etc/opcua/client.key',
'/etc/opcua/ca.pem'
)
->setUserCredentials(
'operator',
getenv('OPCUA_PASSWORD')
)
->connect('opc.tcp://192.168.1.100:4840');
try {
$value = $client->read('ns=2;s=Temperature');
var_dump($value->getValue());
} finally {
$client->disconnect();
}
密码不要直接写进PHP源码。可以从环境变量、Secret管理系统或服务器安全配置中读取。
证书为什么经常导致第一次连接失败
工业OPC UA服务器通常维护自己的信任列表。当PHP客户端第一次拿着新的客户端证书连接时,服务器可能把它放入Rejected或Untrusted目录,需要管理员手动将证书加入Trusted列表。
另一方面,PHP客户端也需要决定是否信任服务器证书。这意味着证书认证是双向信任问题,而不仅仅是在PHP目录中放两个PEM文件。
调试时可以先确认:
- 当前Endpoint支持哪种SecurityPolicy;
- 服务器是否信任PHP客户端证书;
- 客户端是否信任服务器证书;
- 证书时间是否有效;
- 用户名所对应的UserTokenPolicy是否允许当前安全方式。
使用订阅代替不停轮询
如果只需要每30秒读取一次温度,定时执行 read() 没问题。但如果需要实时监控设备状态,不建议写成无限循环每100毫秒执行一次Read。
OPC UA本身提供Subscription和MonitoredItem。客户端建立订阅后,由服务器按照采样和发布机制整理变化数据,客户端通过Publish请求持续获取通知。该库文档也提供了 createSubscription()、createMonitoredItems() 和 publish()。:contentReference[oaicite:4]{index=4}
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use PhpOpcua\Client\ClientBuilder;
$client = ClientBuilder::create()
->connect('opc.tcp://192.168.1.100:4840');
try {
$subscription = $client->createSubscription(
publishingInterval: 500.0
);
$client->createMonitoredItems(
$subscription->subscriptionId,
[
[
'nodeId' => 'ns=2;s=Temperature'
]
]
);
while (true) {
$response = $client->publish();
foreach ($response->notifications as $notification) {
$value = $notification['dataValue']->getValue();
echo date('Y-m-d H:i:s')
. ' Temperature='
. $value
. PHP_EOL;
}
}
} finally {
$client->disconnect();
}
这个程序属于长期运行进程,因此更适合这样执行:
php monitor.php
然后通过systemd、Supervisor、Docker或其他进程管理器保持运行。
不建议把这样的无限循环直接放到普通Nginx + PHP-FPM接口中,因为HTTP请求有超时、Worker占用以及进程生命周期等问题。
Web项目里应该每次请求都连接PLC吗
少量后台操作可以这样做:
浏览器请求
↓
PHP
↓
连接 OPC UA
↓
读取一次
↓
断开连接
↓
返回结果
但实时监控系统更适合:
PLC / OPC UA Server
↓
长期运行 OPC UA Worker
↓
Redis / MySQL / 时序数据库
↓
PHP Web API
↓
浏览器
这样Web请求不需要直接等待PLC建立Session,设备临时离线也不会把网站请求全部拖住。
php-opcua 项目还提供单独的Session Manager,用于在PHP请求之外保持OPC UA Session,这本身也说明持续连接与传统短生命周期Web请求属于不同类型的问题。:contentReference[oaicite:5]{index=5}
一次读取多个节点,不要逐个建立连接
如果页面需要同时显示温度、压力、转速等多个数据,最差的写法是每个变量重新建立一次OPC UA连接。
应该至少复用同一个Session,并使用批量读取功能。该客户端提供 readMulti():
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use PhpOpcua\Client\ClientBuilder;
$client = ClientBuilder::create()
->connect('opc.tcp://192.168.1.100:4840');
try {
$results = $client->readMulti()
->node('ns=2;s=Temperature')->value()
->node('ns=2;s=Pressure')->value()
->node('ns=2;s=PLC/Speed')->value()
->execute();
foreach ($results as $result) {
var_dump($result->getValue());
}
} finally {
$client->disconnect();
}
批量读取的重点不是少写几行PHP,而是减少大量独立请求带来的协议开销。具体一次请求允许读取多少节点,还受到目标OPC UA服务器限制。
连接失败时按什么顺序排查
OPC UA接入出现问题时,不建议一开始就怀疑PHP库。可以按照网络层到应用层逐级排查。
1. 先确认IP和端口
例如服务器Endpoint是:
opc.tcp://192.168.1.100:4840
先确认PHP所在服务器能够访问目标端口:
nc -vz 192.168.1.100 4840
如果TCP端口都不可达,就没有必要继续检查NodeId。
2. 确认Endpoint是否正确
有的OPC UA服务器不只是:
opc.tcp://192.168.1.100:4840
而可能带有服务器路径,例如:
opc.tcp://192.168.1.100:4840/UA/MyServer
应该以实际设备或OPC UA服务器公布的Endpoint为准。
3. 再检查SecurityPolicy
如果服务器关闭None模式,只允许加密Endpoint,那么匿名、无加密的示例自然无法连接。此时应该查询服务器允许的Endpoint和安全策略,而不是反复修改NodeId。
4. 连接成功后再检查NodeId
如果Session已经建立,但Read返回NodeId不存在之类的问题,才应该把注意力转向Namespace和NodeId。
5. 最后检查权限和数据类型
能够Read但不能Write,一般更接近权限、AccessLevel或数据类型问题。
什么时候应该选择open62541,而不是纯PHP
纯PHP客户端最大的优势是部署简单,Composer安装后就可以直接融入Laravel、Symfony或普通PHP项目。不过它并不是所有工业系统的唯一选择。
如果项目具有下面这些特征,可以考虑把OPC UA通信独立出来:
- 需要同时维护大量长期OPC UA连接;
- 通信进程需要全年持续运行;
- 对设备兼容验证和底层协议控制要求很高;
- PHP主要负责管理后台、API和业务展示,而不是实时采集;
- 计划在工业网关或嵌入式Linux设备上部署采集程序。
这时可以采用类似结构:
PLC
↓
open62541采集服务
↓
MQTT / HTTP / Redis / 数据库
↓
PHP业务系统
open62541官方文档明确提供OPC UA客户端实现,项目使用C语言并具有跨平台设计。:contentReference[oaicite:6]{index=6}
这样做的代价是系统多了一层服务,部署和维护复杂度会增加,所以连接数量很少的普通PHP项目没有必要为了“架构更专业”强行增加中间层。
PHP OPC UA项目的推荐结构
如果要把本文示例整理成实际项目,可以把OPC UA连接代码集中封装,而不是在Controller中到处直接调用 ClientBuilder。
opcua-demo/
├── composer.json
├── vendor/
├── src/
│ └── OpcUa/
│ └── OpcUaService.php
├── bin/
│ └── monitor.php
└── public/
└── index.php
OpcUaService.php 负责连接、读取和写入;monitor.php 负责长期订阅;Web Controller或API只调用Service或者读取缓存数据。
这样后面更换PLC、修改证书或者增加重连机制时,不需要把整个PHP项目里的业务代码全部修改一遍。
实际项目中还要特别注意什么
不要把设备密码提交到Git
OPC UA账号、私钥以及证书私钥都不应该直接出现在仓库中。至少使用环境变量,例如:
OPCUA_ENDPOINT=opc.tcp://192.168.1.100:4840
OPCUA_USER=operator
OPCUA_PASSWORD=your-secret-password
不要默认所有设备都支持同样的安全策略
客户端支持某种SecurityPolicy,不代表PLC服务器也支持。最终应该选择客户端和服务器共同支持的安全Endpoint。
不要把None模式直接当成生产默认值
开发网络为了快速验证,可以先使用无加密连接。如果OPC UA流量经过普通办公网络、跨主机网络或其他不完全可信的链路,应优先配置签名和加密。
给通信程序设置超时、重连和日志
PLC重启、交换机切换、网络闪断都是实际运行中会发生的情况。一次能连上并不意味着程序已经适合长期运行。正式采集程序至少应该记录:
- 连接建立和断开时间;
- Endpoint;
- 重连次数;
- NodeId读取错误;
- 服务器返回的状态码;
- 证书或认证错误。
该怎么选择最终实现方案
如果需求是Laravel、Symfony或普通PHP项目直接读取几台PLC的数据,可以从 php-opcua/opcua-client 开始。它不要求额外启动C/C++网关,开发和部署路径最短。
如果当前项目已经统一使用PHP 8.4,也可以把 techdock/opcua 加入兼容性测试,比较两套PHP实现对目标PLC、证书和安全策略的支持情况。
如果系统的核心任务变成“长期连接大量工业设备并持续采集”,则可以把OPC UA通信从PHP Web层拆出来,使用专门Worker,或者使用open62541等协议栈建立独立采集服务,让PHP继续负责API、后台管理和业务逻辑。
总结
PHP实现OPC UA客户端现在并不需要自己处理TCP数据包和OPC UA二进制编码。使用 php-opcua/opcua-client,基本流程可以归纳为:Composer安装客户端库,连接 opc.tcp:// Endpoint,通过Browse找到真实NodeId,再使用Read和Write操作变量;需要实时监控时使用Subscription,需要正式部署时增加证书、安全策略和用户认证。
真正容易出问题的地方通常不是几行PHP语法,而是Endpoint、安全策略、证书信任、Namespace、NodeId、数据类型和节点访问权限。按这些层次逐步验证,比看到报错后反复更换PHP代码更有效。
对于少量设备,纯PHP方案结构简单;对于长期运行、大规模采集,则应该把OPC UA连接管理从普通HTTP请求中分离出去。确定这一点之后,再选择具体开源库,整个系统的结构会清晰很多。
本文涉及的库能力和API已参考 php-opcua/opcua-client 项目与文档、Packagist中的 techdock/opcua 信息以及 open62541 官方资料核对。:contentReference[oaicite:7]{index=7}
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









