Loading... ### 一、前言 原本一直使用的[stepfunc/dnp3](https://github.com/stepfunc/dnp3),但是库是rust开发的,再使用的到c的ffi,扩展起来太麻烦、太头疼了。最近因为新需求,就决定深入研究一下opendnp的代码,实现链路初始化的扩展。 ### 二、定位 显而易见,类`LinkContext`维护了链路层的一个状态,包括FCB翻转、心跳测试、底层连接状态回调等等。 发现如下函数,是在连接建立时触发的事件回调,因此我们增加链路初始化,可以考虑在这个函数体内部动刀。 ```c++ bool LinkContext::OnLowerLayerUp() { if (this->isOnline) { SIMPLE_LOG_BLOCK(logger, flags::ERR, "Layer already online"); return false; } this->isOnline = true; this->RestartKeepAliveTimer(); linkStatus = LinkStatus::UNRESET; //链路状态 listener->OnStateChange(linkStatus); upper->OnLowerLayerUp(); return true; } ``` 再根据`LinkStatus`前面的注释“Enumeration for reset/unreset states of a link layer”,搜索到相关的一个issue:[LinkContext does incorrectly report LinkStatus as UNRESET on link up · Issue #432 · dnp3/opendnp3 · GitHub](https://github.com/dnp3/opendnp3/issues/432),翻阅后,加深对链路初始化的理解,也验证到定位确实没错。 ### 三、动刀 #### 1.链路初始化事件回调派生类增加 在PriLinkLayerStates.h中增加如下类定义: ```c++ class PLLS_ResetLinkStatusWait final : public PriStateBase { MACRO_STATE_SINGLETON_INSTANCE(PLLS_ResetLinkStatusWait); PriStateBase& OnAck(LinkContext& ctx, bool) override; PriStateBase& OnNack(LinkContext& ctx, bool) override; PriStateBase& OnLinkStatus(LinkContext& ctx, bool) override; PriStateBase& OnNotSupported(LinkContext& ctx, bool) override; PriStateBase& OnTxReady(LinkContext&) override; PriStateBase& OnTimeout(LinkContext&) override; }; ``` 在PriLinkLayerStates.cpp中实现相关函数: ```c++ PLLS_ResetLinkStatusWait PLLS_ResetLinkStatusWait::instance; PriStateBase& PLLS_ResetLinkStatusWait::OnAck(LinkContext& ctx, bool) { ctx.CancelTimer(); ctx.FailResetLink(false); return PLLS_Idle::Instance(); } PriStateBase& PLLS_ResetLinkStatusWait::OnNack(LinkContext& ctx, bool) { ctx.CancelTimer(); ctx.FailResetLink(false); return PLLS_Idle::Instance(); } PriStateBase& PLLS_ResetLinkStatusWait::OnLinkStatus(LinkContext& ctx, bool) { ctx.CancelTimer(); //ctx.CompleteKeepAlive(); return PLLS_Idle::Instance(); } PriStateBase& PLLS_ResetLinkStatusWait::OnNotSupported(LinkContext& ctx, bool) { ctx.CancelTimer(); ctx.FailResetLink(false); return PLLS_Idle::Instance(); } PriStateBase& PLLS_ResetLinkStatusWait::OnTxReady(LinkContext&) { // The request link status was successfully sent return *this; } PriStateBase& PLLS_ResetLinkStatusWait::OnTimeout(LinkContext& ctx) { SIMPLE_LOG_BLOCK(ctx.logger, flags::WARN, "Link status reset - response timeout"); ctx.FailResetLink(true); return PLLS_Idle::Instance(); } ``` 在LinkContext.h中,参考`QueueRequestLinkStatus`增加函数`void QueueResetLinkStatus(uint16_t destination)`,并实现如下函数体内容: ```c++ void LinkContext::QueueResetLinkStatus(uint16_t destination) { auto dest = priTxBuffer.as_wseq(); auto buffer = LinkFrame::FormatResetLinkStates(dest, config.IsMaster, destination, this->config.LocalAddr, &logger); FORMAT_HEX_BLOCK(logger, flags::LINK_TX_HEX, buffer, 10, 18); this->QueueTransmit(buffer, true); } ``` 在PriLinkLayerStates.h中,基类`PriStateBase`增加虚函数`virtual PriStateBase& TrySendResetLinkStatus(LinkContext&)`,并在`PLLS_Idle`类中进行实现: ```c++ PriStateBase& PLLS_Idle::TrySendResetLinkStatus(LinkContext& ctx) { ctx.keepAliveTimeout = false; ctx.QueueResetLinkStatus(ctx.config.RemoteAddr); ctx.listener->OnKeepAliveInitiated(); ctx.StartResponseTimer(); return PLLS_ResetLinkStatusWait::Instance(); } ``` 在类`LinkContext`中,调整函数`TryStartTransmission`内容如下: ```c++ void LinkContext::TryStartTransmission() { //确保只在主站模式,且投入开关、链路状态为unreset时,才触发链路重置 if (config.IsMaster && config.ResetAfterConnect &&linkStatus == LinkStatus::UNRESET) { SIMPLE_LOG_BLOCK(this->logger, flags::INFO, "reset remote link!"); this->pPriState = &pPriState->TrySendResetLinkStatus(*this); linkStatus = LinkStatus::WAIT; return; } if (this->keepAliveTimeout) { this->pPriState = &pPriState->TrySendRequestLinkStatus(*this); } if (this->pSegments) { this->pPriState = &pPriState->TrySendUnconfirmed(*this, *pSegments); } } ``` 枚举`LinkStatus`的调整就不浮上来了,太简单了,状态切换如下:unreset->wait->reset 再调整函数`OnLowerLayerUp`即可: ```c++ bool LinkContext::OnLowerLayerUp() { if (this->isOnline) { SIMPLE_LOG_BLOCK(logger, flags::ERR, "Layer already online"); return false; } this->isOnline = true; this->RestartKeepAliveTimer(); linkStatus = LinkStatus::UNRESET; listener->OnStateChange(linkStatus); // reset outstation link //此处实现味道有点冲,不够优雅 TryStartTransmission(); upper->OnLowerLayerUp(); return true; } ``` ### 四、结语 后面深扒了一下,连接建立后,任务调度部分初始化的相关任务启动没有等链路重置完成,虽然任务是启动了但是并未在链路重置任务结束之前下发报文,所以有点无伤大雅就暂不处理了。 这个库个人感觉还是封装的太过了点,学习和理解难度还是不小。 最后修改:2024 年 09 月 07 日 11 : 10 PM © 允许规范转载