声明:原创作品,涉及的开源程序代码学习和研究,严禁用于商业目的。 如有任何问题,欢迎和我交流:408797506@qq.com(微信:408797506)
相关视频学习链接:
为了使程序具有通用性,便于扩展和维护。采用了"模块"插入的思想。将设备业务相关的实现以动态库的形式加载进来。
在上篇文章已经介绍了CWMP的程序处理流程。本篇主要分析一下在CWMP core的程序里如何加载lib库。。比如如何实现调用so库函数, 实现ACS URL解析, CPE get/set函数怎么被调用,怎样添加/删除/更新 obj对象等。
一. 加载lib库
1) 打开动态连接库
还记得上节我们定义的cwmp进程上下文结构体cwmp_context,使用dlopen以指定的模式打开动态库文件,并返回设备library的handle。
//打开设备handle
cwmp_ctx->handle_lib = dlopen(cwmp_ctx->dev_info.dev_lib, RTLD_LAZY);
2) 调用设备相关函数
上节已经介绍了相关设备函数,并定义在device.xml。根据xml定义的tag头取得函数名称,并赋给CWMP进程上下文。比如
cwmp_ctx->dev_info.func_bootstrap = dlsym(cwmp_ctx->handle_lib, attr_value);
cwmp_ctx->dev_info.func_init = dlsym(cwmp_ctx->handle_lib, attr_value);
cwmp_ctx->dev_info.func_get_listenport = dlsym(cwmp_ctx->handle_lib, attr_value);
cwmp_ctx->dev_info.func_get_auth = dlsym(cwmp_ctx->handle_lib, attr_value);
cwmp_ctx->dev_info.func_url_dns_resolve= dlsym(cwmp_ctx->handle_lib, attr_value);
......
二. 设备相关初始化
1 //用于需要平台一开始初始化 2 void dev_init(trf_param_t* param, callback_reg_func_t func, pthread_mutex_t *pmutex_param, LogFunc log_func) 3 { 4 pthread_t thd; 5 monitor_info_t *info = NULL; 6 7 //init local pointer 8 cwmplog_func = log_func; 9 g_reg_func = func; 10 g_root_param = param; 11 g_pmutex_param = pmutex_param; 12 13 info = (monitor_info_t *)malloc_check(sizeof(monitor_info_t)); 14 info->func = func; 15 info->param = param; 16 // info->log_func = log_func; 17 18 closeinout(); 19 20 //初始化CPE 与ACS 连接状态 21 CpeSetValue(NULL, "0", "cpeagent.tr069.acs_status"); 22 23 /* 24 1. define user-defined event code in the device.xml. 25 */ 26 inform_bind(func); 27 28 // monitor_socket_event 线程函数用于与其他程序或进程指定的socket进行通信 29 // 比如源码目录下的sendSocket/client.c 程序,可用于测试或其他 30 // CWMP_SOCK "/opt/cwmp.sock" 31 // pthread_create(&thd, NULL, monitor_socket_event, (void*)info); 32 return; 33 }
主要完成初始化操作:将内存中的根节点参数位置赋给动态库中的全局变量g_root_param指针,以及初始化信号量,日志记录函数,以及FUNC回调函数。 inform_bind(func)函数实现用户自定义的<EventCode>事件,比如电信运营商自定义了X CT-COM BIND事件类型,只用上报正确才能进行工单下发业务。
dev_bootstrap主要用来判断是否是首次连接ACS,如果是把0 BOOTSTRAP和1 BOOT加入Inform事件中,否则把1 BOOT加入Inform事件中。
Inform中带有如下结构信息:
<cwmp:Inform> <DeviceId xsi:type="cwmp:DeviceIdStruct"> <Manufacturer>TEST</Manufacturer> <OUI>A1B2C4</OUI> <ProductClass>TEST_PC</ProductClass> <SerialNumber>821281000054321</SerialNumber> </DeviceId> <Event SOAP-ENC:arrayType="cwmp:EventStruct[2]"> <EventStruct> <EventCode>1 BOOT</EventCode> <CommandKey></CommandKey> </EventStruct> <EventStruct> <EventCode>X CT-COM BIND</EventCode> <CommandKey></CommandKey> </EventStruct> </Event> <MaxEnvelopes>1</MaxEnvelopes> <CurrentTime>2017-01-09T11:53:00</CurrentTime> <RetryCount>0</RetryCount> <ParameterList SOAP-ENC:arrayType="cwmp:ParameterValueStruct[10]"> <ParameterValueStruct> <Name>InternetGatewayDevice.DeviceSummary</Name>
.......
三. 解析ACS URL
同时支持域名和ip地址解析。(放在后续高级部分专门讲解)
四. 每个节点RPC Method
节点结构体如下:
struct trf_param { char name[PARAM_NAME_LEN+1]; //参数名 int type; //参数类型 trf_datatype_e int writable; //是否可写。0:不可写,1:可写,如果object //可以Add,则可写 int max_instance; //属于Object, 最大instance值,-1表示无限制 int notification; //属于Parameter, 0:off,1:passive,2:active unsigned char noti_rw; //属于Parameter, 是否可以设置上报属性,0 不可以 1 可以 unsigned long acl; /*属于Parameter, access list */ TRFGetParamValueFunc getparamval_func; //属于Parameter, 取得参数值函数 TRFSetParamValueFunc setparamval_func; //属于Parameter, 设置参数值函数 TRFAddObjectFunc addobject_func; //属于Object, AddObject TRFDelObjectFunc delobject_func; //属于Object, DeleteObject TRFRefreshFunc refresh_func; //属于Object, 刷新 struct trf_param *parent; //父节点 struct trf_param *child; //子节点 struct trf_param *nextSibling; //兄弟节点 };
每个参数节点拥有自己的属性和方法,并且通过递归方式创建初始化参数树,把初始化后的结果保存在cwmp_context进程上下文中
create_param(&cwmp_ctx->param_root, xmldata->doc->root->firstChild);
五. 其他RPC方法
本文开头已经介绍了调用设备相关函数的方法,其中包括除TR069规范中的升级,恢复出厂,Reboot,Download等方法,我们也可以通过”插件"的形式实现自己的或者私有厂商定义的方法。
<devlib name="/usr/lib/libcwmp.so"></devlib> <auth name="dev_get_auth"></auth> <listenport name="dev_get_listenport"></listenport> <wanparamname name="dev_get_wanparam_name"></wanparamname> <bootstrap name="dev_bootstrap"></bootstrap> <init name="dev_init"></init> <reboot name="dev_reboot"></reboot> <factoryreset name="dev_factoryreset"></factoryreset> <download name="dev_download"></download> <acsstatus name="dev_set_acs_status"></acsstatus> <urldnsresolve name="dev_url_dns_resolve"></urldnsresolve> <upload name="dev_upload"></upload> <cwmpenable name="dev_cwmp_enable"/>
六. 总结
至此,已经基本写完了关于程序设计部分的内容,本篇涉及的代码比较多,尽量不深入代码,而只是围绕协议规范和程序模块化思想分析了部分代码的实现。
七. 参考
1)http://www.cnblogs.com/Anker/p/3746802.html
附:遗留问题
程序开发中在使用dl库中遇到一个问题,若有心的读者能解答,欢迎留言告诉我:
若dlopen打开的库中不存在线程,或线程不在运行状态,则调用dlclose时,进程不会死;
若存在运行时的线程(比如dev_init函数里的 pthread_create(&thd, NULL, monitor_socket_event, (void*)info);),则dlclose时出现segmentation fault。
若不调用dlclose则会出现内存漏洞