onvif学习笔记7:一个C++封装的onvif代码的阅读笔记

在前面的文章《onvif学习笔记4:Windows环境使用gsoap生成onvif框架代码》、《onvif学习笔记5:onvif框架代码初步了解》中,我们了解了如何生成不同的版本的onvif框架代码,同时也看到gSOAP生成的C++版本的代码无论在代码结构还是可扩展性,都比C版本的好很多。笔者无意中接触到一个比较好的onvif工程,本文就使用这个工程代码进行一番粗略的分析,着重介绍其中的服务类的运行。

本文分析的工程使用gSOAP 2.8.15生成,实现上只使用一个端口处理不同服务命令(如设备服务、媒体服务、图像服务,等等)。不同的gSOAP版本生成代码或多或少有不同,但不会影响其实质,很多时候要透过现象看本质,这样对问题将看得更彻底。

使用下列命令:

1
soapcpp2.exe -j -S -I ../gsoap/import/ -I ../gsoap/ onvif.h

生成的框架代码,soap是作为服务类中的一个成员变量。另一种形式在前面文章有介绍,此处不再赘述了。看看设备服务类(DeviceBindingService)的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SOAP_CMAC DeviceBindingService
{ public:
struct soap *soap;
bool own;
/// Constructor
DeviceBindingService();
/// Constructor to use/share an engine state
DeviceBindingService(struct soap*);
/// Constructor with engine input+output mode control
DeviceBindingService(soap_mode iomode);
/// Constructor with engine input and output mode control
DeviceBindingService(soap_mode imode, soap_mode omode);
// .......
};

和另一种形式的差异在soap指针和own变量,看2个构造函数即可知道两者差异:

1
2
3
4
5
6
7
8
9
10
11
DeviceBindingService::DeviceBindingService()
{ this->soap = soap_new();
this->own = true;
DeviceBindingService_init(SOAP_IO_DEFAULT, SOAP_IO_DEFAULT);
}

DeviceBindingService::DeviceBindingService(struct soap *_soap)
{ this->soap = _soap;
this->own = false;
DeviceBindingService_init(_soap->imode, _soap->omode);
}

第一个不带参数的构造函数中,使用soap_new函数创建soap指针,own为true,表明该soap指针属于该类。而第二个构造函数带了soap参数,只是赋值给类中的soap,再将own置为false,表示该soap指针属于其它的地方,不是该类所有。使用2.8.27版本的gSOAP生成代码中,own被改为soap_own,这样更能体现其含义。
下面看看该工程的基本服务类(BaseServer)的Run函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int BaseServer::Run() {
if( !created_ ) {
SIGRLOG(SIGRCRITICAL, "BaseServer::Run Services were not created");
return -1;
}

int iRet = soap_bind(soap_, NULL, m_webservicePort, 100);

if ( SOAP_INVALID_SOCKET == iRet ) {
SIGRLOG(SIGRCRITICAL, "BaseServer::Run Binding on %d port failed", m_webservicePort);
return -1;
}

while(1) {
if( SOAP_INVALID_SOCKET == (iRet = soap_accept(soap_)) ) {
SIGRLOG(SIGRCRITICAL, "BaseServer::Run accepting failed");
return -1;
}

if( SOAP_OK != soap_begin_serve(soap_) ) {
SIGRLOG(SIGRWARNING, "BaseServer::Run serve %d failed at %s", iRet, soap_->action);
soap_destroy(soap_);
continue;
}

for( std::map<OnvifService::Type, IOnvifService*>::iterator it = services_.begin();
it != services_.end(); ++it ) {
if(NULL == it->second)
continue;

if( SOAP_OK == (iRet = it->second->dispatch()) )
break;
}

if( SOAP_OK != iRet )
SIGRLOG(SIGRWARNING, "BaseServer::Run SOAP_Error= %d at %s", iRet, soap_->action);
else
SIGRLOG(SIGRDEBUG2, "BaseServer::Run SOAP_OK at %s", soap_->action);

soap_destroy(soap_);
}

return 0;
}

步骤很简单,首先绑定m_webservicePort端口,然后accept数据,然后调用soap_begin_serve开始处理soap,接着遍历所有服务(每个服务的父类为IOnvifService),执行服务类的dispatch,最后调用soap_destroy结束一次请求。
下面单独看一下设备服务类(DeviceBindingService)的run函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
int DeviceBindingService::run(int port)
{ if (soap_valid_socket(this->soap->master) || soap_valid_socket(bind(NULL, port, 100)))
{ for (;;)
{ if (!soap_valid_socket(accept()) || serve())
return this->soap->error;
soap_destroy(this->soap);
soap_end(this->soap);
}
}
else
return this->soap->error;
return SOAP_OK;
}

其中server函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int DeviceBindingService::serve()
{
unsigned int k = this->soap->max_keep_alive;
do
{
if (this->soap->max_keep_alive > 0 && !--k)
this->soap->keep_alive = 0;
if (soap_begin_serve(this->soap))
{ if (this->soap->error >= SOAP_STOP)
continue;
return this->soap->error;
}
if (dispatch() || (this->soap->fserveloop && this->soap->fserveloop(this->soap)))
{

return soap_send_fault(this->soap);
}
} while (this->soap->keep_alive);

return SOAP_OK;
}

从代码上可以看到,基本服务类BaseServer和单独服务类(包含DeviceBindingService、MediaBindingService)在运行模型上完全是一致的。区别在于BaseServer类保证了程序只有一处监听,遍历服务进行消息派发,而每个服务类单独监听,单独派发本类的消息。
至于该工程涉及到的虚类、继承以及std::map的应用,各位自行阅读代码,不在此文涉及。

参考资料:
onvif-ubuntu工程代码仓库:https://github.com/Ideasrefined/Onvif-ubuntu

李迟 2016.4.7 周四