本文是Linux c++ onvif客户端开发系列文章之一:
- Linux c++ onvif客户端开发(1): 根据wsdl生成cpp源文件
- Linux c++ onvif客户端开发(2): 获取摄像头H264/H265 RTSP地址
- Linux c++ onvif客户端开发(3): 扫描设备
- Linux c++ onvif客户端开发(4): 扫描某个设备是否支持onvif
- Linux c++ onvif客户端开发(5):gsoap内存管理
可以先先看一下gsoap内存管理这篇文章。
1. 先定义一个命名空间
namespace onvif {
}
2. 定义一个fault结构, 表示xml中soap:Fault错误信息
struct Fault {
std::string code;
std::string subcode;
std::string str; // string
std::string detail;
};
一个典型的Fault信息格式如下
<s:Envelope
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:sc="http://www.w3.org/2003/05/soap-encoding"
xmlns:ter="http://www.onvif.org/ver10/error"
xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2"
xmlns:wsrf-bf="http://docs.oasis-open.org/wsrf/bf-2"
xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery">
<s:Body>
<s:Fault>
<s:Code>
<s:Value>s:Sender</s:Value>
<s:Subcode>
<s:Value>ter:ActionNotSupported</s:Value>
<s:Subcode>
<s:Value>ter:NotImplemented</s:Value>
</s:Subcode>
</s:Subcode>
</s:Code>
<s:Reason>
<s:Text xml:lang="en">This optional method is not implemented</s:Text>
</s:Reason>
</s:Fault>
</s:Body>
</s:Envelope>
3. 对struct soap进行包装
class Soap {
public:
Soap(int timeout);
~Soap();
void InitHeader();
void InitProbeType(struct wsdd__ProbeType *probe);
int Error() { return soap_->error; }
// 返回的xml中soap:Fault
std::string FaultString();
std::string FaultCode();
std::string FaultSubcode();
std::string FaultDetail();
struct soap *soap() const;
// c++中struct soap的析构函数会自动调用
void Destroy();
// 填写Fault信息
void FillFault(Fault *fault = nullptr);
private:
struct soap *soap_;
};
4. 实现
#define SOAP_PROBE_TO "urn:schemas-xmlsoap-org:ws:2005:04:discovery"
#define SOAP_PROBE_ACTION \
"http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe"
#define SOAP_MCAST_ADDR "soap.udp://239.255.255.250:3702" // onvif规定的组播地址
#define SOAP_ITEM "" // 寻找的设备范围
#define SOAP_TYPES "dn:NetworkVideoTransmitter" // 寻找的设备类型
#define SOAP_SOCK_TIMEOUT (2) // socket超时时间(单秒秒)
namespace onvif {
namespace util {
// 定义 split() 函数,将字符串按指定分隔符分割成多个子字符串并存入
// vector<string> 中
static void Split(const std::string &s, char delim,
std::vector<std::string> &elems) {
std::istringstream iss(s);
for (std::string item; getline(iss, item, delim);) {
if (!item.empty()) {
elems.push_back(item);
}
}
}
int CountChar(const std::string &s, char c) {
auto count = std::count(s.begin(), s.end(), c);
return count;
}
} // namespace util
Soap::Soap(int timeout) {
// There is no need to call soap_init to initialize the context
// allocated with soap_new, since soap_new initializes the allocated
// context.
// https://www.genivia.com/doc/guide/html/group__group__context.html#ga87c20488b2dc680aaa7689b1d024989c
soap_ = soap_new();
if (!soap_)
throw std::runtime_error("soap_new() fail");
soap_set_namespaces(soap_, namespaces); // 设置soap的namespaces
// 不正常数据设置成5s
if (timeout <= 0)
timeout = SOAP_SOCK_TIMEOUT;
soap_->recv_timeout = timeout; // 设置超时(超过指定时间没有数据就退出)
soap_->send_timeout = timeout;
soap_->connect_timeout = timeout;
#if defined(__linux__) || \
defined(__linux) // 参考https://www.genivia.com/dev.html#client-c的修改:
soap_->socket_flags = MSG_NOSIGNAL; // To prevent connection reset errors
#endif
soap_set_mode(soap_,
SOAP_C_UTFSTRING); // 设置为UTF-8编码,否则叠加中文OSD会乱码
}
Soap::~Soap() {
soap_destroy(soap_); // delete managed C++ objects
soap_end(soap_); // delete managed memory。soap_malloc
soap_done(soap_); // stacked
soap_free(soap_); /* we're done with the context */
}
/**
* @brief 填充Header, 用于Probe操作
*
<SOAP-ENV:Header>
<wsa:MessageID>urn:uuid:dc8f9f8a-05b2-45c2-a63e-f5b47636af83</wsa:MessageID>
<wsa:To
SOAP-ENV:mustUnderstand="true">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>
<wsa:Action
SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>
</SOAP-ENV:Header>
*/
void Soap::InitHeader() {
// T * soap_new_T(struct soap*) allocates and initializes data of type T
// in context-managed heap memory, managed data is deleted with
// soap_destroy (deletes C++ objects) and soap_end (deletes all other
// data), and you can also use soap_malloc to allocate uninitialized
// context-managed memory.
struct SOAP_ENV__Header *header = soap_new_SOAP_ENV__Header(soap_);
header->wsa__MessageID = (char *)soap_wsa_rand_uuid(soap_);
header->wsa__To = soap_strdup(soap_, SOAP_PROBE_TO);
header->wsa__Action = soap_strdup(soap_, SOAP_PROBE_ACTION);
soap_->header = header;
}
/**
* @brief 填充body Probe数据, 用于Probe操作
* <SOAP-ENV:Body>
<wsdd:Probe>
<wsdd:Types>dn:NetworkVideoTransmitter</wsdd:Types>
<wsdd:Scopes/>
</wsdd:Probe>
</SOAP-ENV:Body>
* @param probe
*/
void Soap::InitProbeType(struct wsdd__ProbeType *probe) {
// 用于描述查找哪类的Web服务
struct wsdd__ScopesType *scope = soap_new_wsdd__ScopesType(soap_);
// soap_default_wsdd__ScopesType(soap_, scope); // 设置寻找设备的范围
scope->__item = soap_strdup(soap_, "");
probe->Scopes = scope;
probe->Types = soap_strdup(soap_, SOAP_TYPES); // 设置寻找设备的类型
}
std::string Soap::FaultString() {
const char *fault_string = soap_fault_string(soap_);
if (!fault_string)
return std::string();
else
return std::string(fault_string);
}
std::string Soap::FaultCode() {
const char **code = soap_faultcode(soap_);
if (code && *code)
return std::string(*code);
else
return std::string();
}
std::string Soap::FaultSubcode() {
const char *subcode = soap_fault_subcode(soap_);
if (!subcode)
return std::string();
else
return std::string(subcode);
}
std::string Soap::FaultDetail() {
const char *detail = soap_fault_detail(soap_);
if (!detail)
return std::string();
else
return std::string(detail);
}
struct soap *Soap::soap() const {
return soap_;
}
void Soap::Destroy() { soap_->destroy(); }
void Soap::FillFault(Fault *fault) {
if (!fault)
return;
// str
const char *fault_string = soap_fault_string(soap_);
if (fault_string)
fault->str.assign(fault_string);
// code
const char **code = soap_faultcode(soap_);
if (code && *code)
fault->code.assign(*code);
// subcode
const char *subcode = soap_fault_subcode(soap_);
if (subcode)
fault->subcode.assign(subcode);
// detail
const char *detail = soap_fault_detail(soap_);
if (detail)
fault->detail.assign(detail);
}
} // namespace onvif