文章目录
- 1.背景
- 2.解决方案
- 3.异步连接
- 4.注意事项
- 4.1.线程问题
- 4.2.UA_Client_run_iterate
1.背景
目前在利用open62541.h/open62541.c编写了一个与PLC进行OPCUA通讯的上位机程序。
上位机这边会定时对PLC的某个opcua变量进行写操作。但是假如PLC离线或者说拔掉网线,上位机就会直接崩溃死机,并且报如下的错误:
[2024-08-12 10:07:25.528 (UTC+0800)] warn/channel Connection 2516 | SecureChannel 28 | Receiving the response failed with StatusCode BadConnectionClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/client Received Publish Response with code BadSecureChannelClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/client Received Publish Response with code BadSecureChannelClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/client Received Publish Response with code BadSecureChannelClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/client Received Publish Response with code BadSecureChannelClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/client Received Publish Response with code BadSecureChannelClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/client Received Publish Response with code BadSecureChannelClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/client Received Publish Response with code BadSecureChannelClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/client Received Publish Response with code BadSecureChannelClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/client Received Publish Response with code BadSecureChannelClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/client Received Publish Response with code BadSecureChannelClosed
[2024-08-12 10:07:25.528 (UTC+0800)] warn/channel Connection 0 | SecureChannel 0 | Could not receive with StatusCode BadConnectionClosed
[2024-08-12 10:07:25.528 (UTC+0800)] info/client Client Status: ChannelState: Closed, SessionState: Created, ConnectStatus: Good
有没有什么办法可以监控client的状态,了解其是什么时候掉线,掉线了我就不写就行了。
2.解决方案
幸亏,是有这么一个回调函数的,参考官方的例子【open62541/examples/client_async.c】可以发现,我们可以对UA_ClientConfig中的stateCallback进行赋值,也就是注册一个状态回调函数,从而获取客户端的状态变化。
static void
onConnect(UA_Client *client, UA_SecureChannelState channelState,
UA_SessionState sessionState, UA_StatusCode connectStatus) {
printf("Async connect returned with status code %s\n",
UA_StatusCode_name(connectStatus));
}
---
UA_ClientConfig *cc = UA_Client_getConfig(client);
cc->stateCallback = onConnect;
一般检测channelState就行
// 状态变化回调函数,可以通过这个监测客户端是否断开连接
// 此回调函数的线程,貌似就是UA_Client_run_iterate所在的线程?起始应该不是,恐怕是在哪个线程调用了UA_Client相关的函数,就在那个线程;
static void
onStateChanged(UA_Client *client,
UA_SecureChannelState channelState,
UA_SessionState sessionState,
UA_StatusCode connectStatus)
{
if(channelState == UA_SECURECHANNELSTATE_CLOSED) // 连接已断开
{
qDebug() << "callback thread:" << QThread::currentThread();
qDebug() << "连接已断开--------" << QDateTime::currentDateTime();
---
}
}
3.异步连接
在实际使用中,假如opcua服务端是离线的话,执行UA_Client_connect
会导致线程卡住很长一段时间。假如不希望卡住的话,得使用异步连接:UA_Client_connectAsync
;
使用UA_Client_connectAsync
后,需要在stateCallback
函数中判断是否连接上。完全连接上的话,
UA_SessionState sessionState
会等于UA_SESSIONSTATE_ACTIVATED
;
static void
onStateChanged(UA_Client *client,
UA_SecureChannelState channelState,
UA_SessionState sessionState,
UA_StatusCode connectStatus)
{
}
假如你在onStateChanged
中将channelState、sessionState打印出来的话,会发现channelState先从上往下满足下面的顺序:
然后是sessionState从上往下满足下面的顺序:
此外,一定要记得在某个地方周期地调用UA_Client_run_iterate
,否则,不会进入回调函数。
4.注意事项
4.1.线程问题
这个opcua库对多线程的处理比较差,很容易造成冲突。一定要自己加个互斥锁,否则程序很容易就崩掉。
4.2.UA_Client_run_iterate
一定要在某个地方周期调用。但是最好不是在子线程中调用,除非你有把握控制好线程冲突(比如加互斥锁之类)。
参考:
【open62541/examples/client_async.c】