上篇文章 简单介绍了cms61850,本篇主要谈谈该业务的实现。github我已开源了cms61850的部分功能,可方便大家学习。可直接github搜索
本代码采用c++11编写,组件化模式设计。为更方便大家理解,核心部分的类图如下:

ITcpServerHandleMessage是处理tcp消息的接口,实际报文由CCMSMessageHandle处理。IService是各项具体业务处理的接口类,聚合于CServiceManager类中。
可看实际代码如下:
/**
* @file ServiceManager.h
* @author (linuxzq93@163.com)
* @brief cms61850服务的主处理部分,完成底层业务函数的回调的注册及调用
* @version 1.0
* @date 2023-03-07
*
* @copyright Copyright (c) 2023
*
*/
#ifndef __CMS61850_SERVICE_SERVICEMANAGER_H__
#define __CMS61850_SERVICE_SERVICEMANAGER_H__
#include "SingTon/SingTon.h"
#include "IService.h"
#include <map>
#include <string>
#include "Function/Bind.h"
#include "APER.h"
#include "asn1/ServiceError.h"
#include "Net/TcpServer.h"
#include "json/value.h"
namespace cms {
class CServiceManager {
SINGTON_DECLAR(CServiceManager);
public:
using Func = TFunction<ServiceError, const std::string&, const NetMessage&, std::string&>;
enum Status {
NegotiateFail,
AssociateFail,
SUCCESS
};
public:
bool init();
bool start();
void setTcpServer(const std::shared_ptr<base::CTcpServer> &pServer) { m_tcpServer = pServer; }
bool getClientInfo(int clientId, base::clientInfo &info) { return m_tcpServer->getClientInfo(clientId, info); }
bool closeClient(int clientId) { return m_tcpServer->closeClient(clientId); }
void setStatus(int clientId, Status status) { m_mapStatus[clientId] = status; }
bool attachService(const std::string &name, IService *pSrv);
bool attachFunc(int code, const std::string &name, const Func &func);
/// 0 success -1 failure -2 invalid
int dealPDU(int code, int clientId, const uint8_t *buf, int len, std::string &response);
private:
/// 注册服务
std::map<std::string, IService*> m_mapSrv;
/// 注册处理的函数
std::map<int, std::pair<std::string, Func>> m_mapFunc;
std::map<int, Status> m_mapStatus;
std::shared_ptr<base::CTcpServer> m_tcpServer;
int m_errNum;
int m_apduSize;
int m_asduSize;
};
}
#endif /* __CMS61850_SERVICE_SERVICEMANAGER_H__ */
其中m_mapSrc存储的是各类业务的指针,m_mapFunc是处理具体功能的函数,也就是具体的服务码。ServiceManager初始化或启动时,会遍历启动所有的服务
bool CServiceManager::init()
{
for (const auto &iter : m_mapSrv)
{
if (!iter.second->init())
{
errorf("%s service init failed\n", iter.first.c_str());
return false;
}
}
base::IConfigManager *pConfig = base::CComponentManager::instance()->getComponent<base::IConfigManager>("ConfigManager");
Json::Value cfgValue;
pConfig->getConfig("CMS61850", cfgValue);
m_errNum = cfgValue["errorNum"].asInt();
m_apduSize = cfgValue["associate"]["apduSize"].asInt();
m_asduSize = cfgValue["associate"]["asduSize"].asInt();
return true;
}
bool CServiceManager::start()
{
for (const auto &iter : m_mapSrv)
{
if (!iter.second->start())
{
errorf("%s service init failed\n", iter.first.c_str());
return false;
}
}
return true;
}
而当具体业务初始化时,会再次注册相应的处理函数
bool CReport::init()
{
CServiceManager::instance()->attachFunc(91, "GetBRCBValues", base::function(&CReport::getBRCBValue, this));
CServiceManager::instance()->attachFunc(93, "GetURCBValues", base::function(&CReport::getURCBValue, this));
CServiceManager::instance()->attachFunc(94, "SetURCBValues", base::function(&CReport::setURCBValue, this));
auto *pEvent = base::CComponentManager::instance()->getComponent<base::IEventManager>("EventManager");
pEvent->attach("cmsSocket", base::IEventManager::Proc(&CReport::socketEvt, this));
m_vecUrcbFunc.emplace_back(&CReport::writeGI, this);
return true;
}
可以看到Report这个服务注册了服务码91 93 94的接口,以93为例,我们只需要把这个接口要实现的内容,填写至getURCBValue这个函数中即可
ServiceError CReport::getURCBValue(const std::string &funcName, const NetMessage &message, std::string &response)
{
CSafeStruct<GetURCBValues_RequestPDU> reqPtr;
if (!Decode(reqPtr, message.buf, message.len))
{
errorf("decode %s request failed\n", funcName.c_str());
return ServiceError_decode_error;
}
PrintAPER(reqPtr);
CSafeStruct<GetURCBValues_ResponsePDU> respPtr;
BOOLEAN_t *flag = CallocPtr(BOOLEAN_t);
respPtr->moreFollows = flag;
for (int i = 0; i < reqPtr->reference.list.count; i++)
{
auto *member = CallocPtr(GetURCBValues_ResponsePDU__urcb__Member);
ASN_SEQUENCE_ADD(&respPtr->urcb.list, member);
auto &reqRef = reqPtr->reference.list.array[i];
std::string strRef;
if (reqRef->buf != NULL)
{
strRef = (char *)reqRef->buf;
}
std::vector<std::string> result;
splitStr("/", strRef, result);
auto *ldInfo = m_scl->getLdInfo(result[0]);
if (NULL == ldInfo)
{
warnf("ldName[%s] not find\n", result[0].c_str());
member->present = GetURCBValues_ResponsePDU__urcb__Member_PR_error;
member->choice.error = ServiceError_instance_not_available;
continue;
}
std::vector<std::string> result1;
splitStr(".", result[1], result1);
auto *lnInfo = m_scl->getLnInfo(ldInfo, result1[0]);
if (NULL == lnInfo)
{
warnf("lnName[%s] not find\n", result1[0].c_str());
continue;
}
auto *rpInfo = m_scl->getReportInfo(lnInfo, result1[1]);
if (NULL == rpInfo)
{
continue;
}
if (!fillURReport(rpInfo, member))
{
warnf("fill report[%s] failed\n", result1[1].c_str());
continue;
}
}
PrintAPER(respPtr);
if (!Encode(respPtr, response))
{
errorf("encode %s failed\n", funcName.c_str());
return ServiceError_failed_due_to_communications_constraint;
}
return ServiceError_no_error;
}
那么这个回调函数是在哪调用的呢?这个是在接受tcp消息后,由ServiceManager自动调用
auto &tmp = m_mapFunc[code];
NetMessage message;
message.buf = &buf[6];
message.clientId = clientId;
message.len = len;
infof("enter %s\n", tmp.first.c_str());
ServiceError ret = tmp.second(tmp.first, message, response);
infof("leave %s, ret is %d\n", tmp.first.c_str(), ret);
如果大家对观察者模式比较了解的话,代码看起来也比较快。当然这些核心代码也可以不用了解,只需要自己定义业务类,然后照葫芦画瓢,注册自己的服务码处理接口就可以。
代码还在不断更新中,欢迎各位给star或者提PR。