• CWMP开源代码研究5——CWMP程序设计思想


    声明:本文涉及的开源程序代码学习和研究,严禁用于商业目的。 如有任何问题,欢迎和我交流。(企鹅号:408797506)

    本文介绍自己用过的ACS,其中包括开源版(提供下载包)和商业版(仅提供安装包下载,没有源码)

    参考:

    1) http://www.docin.com/p-1306443672.html

    2) http://www.easycwmp.org/

    一. 背景

      程序设计的思想来自于easycwmp官网,看过或者用过easycwmp的工程师应该都知道,该开源代码还有商业版,而且价格不菲(前公司曾经想要购买后放弃),easycwmp官网如是说:DataModel is developped with shell as free solution and with C as commercial solution.。开源代码用来学习还是值得的,若是用于商业产品可能就会显得"力不从心"。幸运的是,我有机会阅读了Works Systems公司和broadcom公司的tr069代码,架构设计与easycwmp的设计"如出一辙",下图是easycwmp官网的程序架构图。但是,个人对于Works Systems公司的代码"情有独钟"并且进行了重新开发和利用,使得程序更加高效易用和移植性。基于此,本文就着重介绍在商业代码中如何高效,便捷的实现DataModel 和CWMP core分离,给读者一个程序设计的思路。 后续若有机会可以介绍一下broadcom公司的程序设计仅供参考。本质上大同小异。

    二. 程序设计概要

      对于单一的产品线程序采用"单进程多线程"的思想。这样比较容易简洁,而且方便维护。若是多功能的产品,即一个设备上需要运行多个"CWMP进程", 那么我们使用了创建"多个子进程"的方法,每个子进程根据配置文件的不同从而实现设备的不同需求(该功能待完善)。比如现在家庭或者企业网关产品越来越需要"智慧""智能"的需求,如何让设备与手机互连互通,保证安全方便高效的前提下,既可以被运营商(卖方)管理,同时又可以被自己(买方)控制管理,这是一个值得思考的问题,也是工程师需要考虑的技术。

      下图是单一产品的tr069程序处理流程。大致分为:配置文件解析模块,日志模块,设备xml解析模块,任务模块以及事件处理模块(有关联),多线程模块(可插入模块)等。

       原则上,CWMP core的程序代码不需要修改,主要是根据客户的需求修正或者进行"插入式的"新增事件类型和模块化处理。而设备相关的程序,我们封装成了一个动态库(libcwmp.so),便于独立编译和维护开发。

                                                                                         (附: 高清PDF版下载路径http://download.csdn.net/detail/eryunyong/9731487

    3.1 配置文件解析(libconf)

      根据配置文件的全路径和内容初始化数据结构,使用例子如下:

    1 char      conf_file[PATH_MAX] = {0};
    2 conf_t   *tmp = NULL;
    3 
    4 tmp = conf_load(conf_file);
    5 count = conf_get_int(tmp, "global:count", 0);
    View Code

     3.2 日志模块

      为了便于和Linux的syslog统一和管理,这里定义的日志等级与syslog一致。 

       CWMP_LOG_EMERG        ---------------------->        EMERG = 0
       CWMP_LOG_ALERT        ---------------------->         ALERT = 1
       CWMP_LOG_CRIT        ---------------------->           CRIT  = 2
       CWMP_LOG_ERROR        ---------------------->        ERROR = 3
       CWMP_LOG_WARN        --------------------->          WARN  = 4
       CWMP_LOG_NOTICE        --------------------->        NOTICE= 5
       CWMP_LOG_INFO        --------------------->            INFO  = 6
       CWMP_LOG_DEBUG        --------------------->        DEBUG = 7

    3.3 XML解析模块

      使用libexpat库函数解析设备XML格式文件,以及CWMP和ACS之间交换的SOAP消息。device.xml文件内容如下:

     1 <TR069 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
     2   <trf>
     3     <obj name="InternetGatewayDevice">
     4       <param name="DeviceSummary" getval_func="CpeGetDeviceSummary"></param>
     5       <param name="LANDeviceNumberOfEntries" type="2" getval_func="CpeGetLANDeviceNumberOfEntries"></param>
     6       <param name="WANDeviceNumberOfEntries" type="2" getval_func="CpeGetWANDeviceNumberOfEntries"></param>
     7       <obj name="DeviceInfo" noti_rw="1">
     8         <param name="SpecVersion" getval_func="CpeGetDeviceInfoSpecVersion"/>
     9         <param name="HardwareVersion" getval_func="CpeGetDeviceInfoHardwareVersion"></param>
    10         <param name="SoftwareVersion" getval_func="CpeGetDeviceInfoSoftwareVersion"></param>
    11         <param name="Manufacturer"      getval_func="CpeGetDeviceInfoManufacturer"></param>
    12         <param name="SerialNumber"      getval_func="CpeGetDeviceInfoSerialNumber"></param>
    13         <param name="ManufacturerOUI" getval_func="CpeGetDeviceInfoManufacturerOUI"></param>
    14         <param name="ProvisioningCode" rw="1" getval_func="CpeGetDeviceInfoProvisioningCode" setval_func="CpeSetDeviceInfoProvisioningCode"></param>
    15         <param name="ProductClass" getval_func="CpeGetDeviceInfoProductClass"></param>
    16         <param name="DeviceType" getval_func="CpeGetDeviceInfoDeviceType"></param>
    17      <param name="ModelName" getval_func="CpeGetDeviceInfoModelName"></param>
    18         <param name="CpeWANAddress" noti_rw="1" rw="1" getval_func="CpeGetCpeWANAddress" setval_func="CpeSetCpeWANAddress"/>
    19       </obj>
    20       <obj name="ManagementServer">
    21         <param name="ConnectionRequestURL"                                   getval_func="CpeGetManagementServerConnectionRequestURL"></param>
    22         <param name="ConnectionRequestUsername"     rw="1"    noti_rw="1"    getval_func="CpeGetManagementServerConnectionRequestUsername" setval_func="CpeSetManagementServerConnectionRequestUsername"></param>
    23         <param name="ConnectionRequestPassword"     rw="1"    noti_rw="1"    getval_func="CpeGetManagementServerConnectionRequestPassword" setval_func="CpeSetManagementServerConnectionRequestPassword"></param>
    24         <param name="Username"                      rw="1"    noti_rw="1"    getval_func="CpeGetManagementServerUsername"          setval_func="CpeSetManagementServerUsername"></param>
    25         <param name="Password"                      rw="1"    noti_rw="1"    getval_func="CpeGetManagementServerPassword"          setval_func="CpeSetManagementServerPassword"></param>
    26         <param name="ParameterKey"                   getval_func="CpeGetManagementServerParameterKey"      setval_func="CpeSetManagementServerParameterKey"/>
    27         <param name="URL"                           rw="1"    noti_rw="1"    getval_func="CpeGetManagementServerUrl"               setval_func="CpeSetManagementServerUrl"></param>
    28         <param name="PeriodicInformEnable"          rw="1"  noti_rw="1"  type="3" getval_func="CpeGetManagementServerPeriodicInformEnable"      setval_func="CpeSetManagementServerPeriodicInformEnable"></param>
    29         <param name="PeriodicInformInterval"        rw="1"  noti_rw="1"  type="2" getval_func="CpeGetManagementServerPeriodicInformInterval"    setval_func="CpeSetManagementServerPeriodicInformInterval"></param>
    30         <param name="PeriodicInformTime"            rw="1"          type="4" getval_func="CpeGetManagementServerPeriodicInformTime"        setval_func="CpeSetManagementServerPeriodicInformTime"></param>
    31       </obj>
    32 
    33       <obj name="Time">
    34         <param name="Enable"          rw="1"  type="3"     getval_func="CpeGetTimeEnable"          setval_func="CpeSetTimeEnable"></param>
    35         <param name="NTPServer1"              rw="1"     getval_func="CpeGetTimeNTPServer1"      setval_func="CpeSetTimeNTPServer1"></param>
    36         <param name="CurrentLocalTime"       type="4"             getval_func="CpeGetTimeCurrentLocalTime"></param>
    37       </obj>
    38       <obj name="X_CT-COM_MonitorCollector">
    39          <param name="Enable"      noti_rw="1"     rw="1" type="3"   getval_func="CpeGet_MonitorEnable"                 setval_func="CpeSet_MonitorEnable"></param>
    40          <obj name="MonitorConfig"  rw="1" addobj_func="TRF_Add_MonitorConfig" delobj_func="TRF_Del_MonitorConfig"  refresh_func="TRF_Refresh_MonitorConfig">
    41            <obj name="0">
    42              <param name="ParaList"  noti_rw="1"     rw="1"   getval_func="CpeGet_MonitorConfig_ParaList"                 setval_func="CpeSet_MonitorConfig_ParaList"></param>
    43              <param name="TimeList"   rw="1"  type="2"          getval_func="CpeGet_MonitorConfig_TimeList"      setval_func="CpeSet_MonitorConfig_TimeList"></param>
    44            </obj>
    45         </obj>
    46       </obj>    
    47       <obj name="LANDevice">
    48         <obj name="1">
    49           <param name="LANEthernetInterfaceNumberOfEntries" type="2" getval_func="CpeGetLANEthernetInterfaceNumberOfEntries"/>
    50         </obj>
    51       </obj>
    52     <obj name="ObjTest"   rw="1"  addobj_func="TRF_Add_ObjTest" delobj_func="TRF_Del_ObjTest" refresh_func="TRF_Refresh_ObjTest">
    53         <obj name="0">
    54             <param name="TestEnabled" rw="1" type="3" getval_func="CpeGetObjTest_TestEnabled" setval_func="CpeSetObjTest_TestEnabled"/>
    55         </obj>
    56     </obj>
    57     </obj>
    58   </trf>
    59 
    60   <devlib name="/usr/lib/libcwmp.so"></devlib>
    61   
    62   <auth name="dev_get_auth"></auth>
    63   
    64   <listenport name="dev_get_listenport"></listenport>
    65   <wanparamname name="dev_get_wanparam_name"></wanparamname>
    66   
    67   <bootstrap name="dev_bootstrap"></bootstrap>  
    68   <init name="dev_init"></init>  
    69   <reboot name="dev_reboot"></reboot>
    70 
    71   <factoryreset name="dev_factoryreset"></factoryreset>
    72   <download name="dev_download"></download>
    73   <acsstatus name="dev_set_acs_status"></acsstatus>
    74   <urldnsresolve name="dev_url_dns_resolve"></urldnsresolve>
    75   
    76   <upload name="dev_upload"></upload>
    77   <cwmpenable name="dev_cwmp_enable"/>
    78  
    79   <informlist>
    80     <inform name="InternetGatewayDevice.DeviceInfo.ModelName"/>
    81     <inform name="InternetGatewayDevice.DeviceInfo.DeviceType"/>
    82   </informlist>
    83 
    84   <eventlist>
    85     <event name="X CT-COM BIND"></event>
    86   </eventlist>
    87 
    88 
    89 </TR069>
    View Code

     InternetGatewayDevice是整个参数树的根。obj表示这是一个对象,obj可以读,可以写,当objname0时,表示该obj可以是个模板,为创建后面的实例提供一个模板,当ACS查询时,不会把obj name0的Obj发送给ACS。Objrw=1,表示该obj可以添加子obj,通过addobj_func来添加,通过delobj_func来删除,refresh_func表示刷新该obj下的信息。 Objnoti_rw =1认为可以设置该obj的属性,譬如notify属性,如果设置了obj的属性,则认为该obj下的所以子树都有该属性。Param表示一个参数项,参数可以读,可以写,通过getval_func来读,通过setval_func来写。noti_rw=1认为可以设置该Param的属性,譬如notify属性。Type的含义如下:

    string             ------------------------->         0
    int                -------------------------->         1
    unsigned int       ----------------------->         2
    bool               ------------------------->         3
    datetime           ----------------------->         4
    base64             ----------------------->         5
    long               ------------------------->         6
    unsigned long      ---------------------->         7
    hex binary         ----------------------->         8
    object             ------------------------->         9

    3.4 TASK任务模块

      根据任务队列中的消息类型进行处理,把需要发送给ACS的事件event消息加入事件队列。

     1 //诊断
     2 #define TASK_DIAG           1
     3 //重启
     4 #define TASK_REBOOT         2
     5 //恢复出厂设置
     6 #define TASK_FACTORY        3
     7 //download
     8 #define TASK_DOWNLOAD       4
     9 //upload
    10 #define TASK_UPLOAD         5
    11 //change ACS URL
    12 #define TASK_CHANGE_ACS_URL 6
    13 #define TASK_SUBDEVICE      7
    14 #define TASK_ADD_EVENT      8
    15 #define TASK_ADD_INFORM     9
    16 #define TASK_CLEAR_EVENT    10
    17 
    18 #define TASK_VPN_RESTART    20
    19 #define TASK_SYSLOG_RESTART 21
    20 #define TASK_FIREWARE_RESTART 22
    21 
    22 #define TASK_OTHER          99
    View Code

    3.5 事件处理模块

       使用event_handle函数来处理事件,通过信号量来等待是否需要处理的事件,以及从事件队列中获取处理的事件。同理,在其他线程函数中,通过置信号量,将事件加入队列中来通知该模块处理。

        STATUS_IDLE = 0,        /* 空闲状态 */
        STATUS_INIT,            /* 初始化,获取ACS的URL,以及发送Inform消息*/
        STATUS_CONN,            /* CPE和ACS处于连接状态,并处理ACS下发的任务 */
        STATUS_ERROR,         /* 发生错误 */
        STATUS_FINS,         
    /* 结束事件处理 */

    3.6 其他多线程模块

       为了实现"低耦合高内聚"的模块化思想,程序设计采用了多线程来实现。比如:周期上报Inform事件, 根据tr069规范监测参数变化的上报事件,检测WAN口地址变化的事件,STUN线程,DHCP发现ACS地址事件。

    四. 数据结构

     1) cwmp_context结构体是CWMP进程处理的上下文,主要包括初始化设备参数树,Value change,监视参数变化,记录事件等。

     1 struct cwmp_context{
     2     file_context_t      file_ctx;           //配置文件
     3     trf_param_t         param_root;         //参数树根节点
     4     dev_info_t          dev_info;           //设备信息
     5 
     6     void                *handle_lib;        //设备library的handle
     7     
     8     int                 acs_port;           //监听ACS的端口
     9     int                 acs_retrycount;     //连接ACS重试次数
    10     int                 notify_interval;    //监视参数变化的间隔时间
    11     
    12     pthread_mutex_t     mutex_attr;
    13     hash_t              *ht_attr;           //记录参数属性,需要上报的。
    14 
    15     pthread_mutex_t     mutex_val_change;  
    16     hash_t              *ht_val_change;     //记录Value Change
    17 
    18     char                **inform_array;     //需要上报的参数项数组
    19     int                 inform_count;       //需要上报的参数项总数
    20 
    21     pthread_mutex_t     mutex_inform_tmp;
    22     char                **inform_array_tmp; //临时需要上报的参数项数组
    23     int                 inform_count_tmp;   //临时需要上报的参数项总数
    24     
    25     pthread_mutex_t     mutex_evt;
    26     int                 evt_count;
    27     event_info_t        *evt_array;         //记录事件
    28     event_global_t      evt_global_info;    //记录由于重启需要保存的信息
    29     sem_t               sem_send_acs;       //发送给acs信息的信号量
    30     
    31     
    32     pthread_mutex_t     mutex_task;         //for task_list
    33     list_t              *task_list;         //task list
    34     sem_t               sem_task;
    35 
    36     pthread_mutex_t     mutex_param;        //对参数进行加锁,防止多线程操作时导致程序不稳定
    37                                             //仅在对参数有操作的线程中加锁
    38     trans_t             transfer_info;      //用于Download和Upload
    39 };
    View Code

      2)EventType主要定义了规范中的事件类型

     1 typedef enum
     2 {
     3     EVENT_BOOTSTRAP = 0,
     4     EVENT_BOOT,
     5     EVENT_PERIODIC,
     6     EVENT_SCHEDULED,
     7     EVENT_VALUECHANGE,
     8     EVENT_KICKED,
     9     EVENT_CONNECTIONREQUEST,
    10     EVENT_TRANSFERCOMPLETE,
    11     EVENT_DIAGNOSTICSCOMPLETE,
    12     EVENT_REQUESTDOWNLOAD,
    13     EVENT_AUTONOMOUSTRANSFERCOMPLETE,
    14     EVENT_MREBOOT,
    15     EVENT_MSCHEDULEINFORM,
    16     EVENT_MDOWNLOAD,
    17     EVENT_MUPLOAD,
    18     EVENT_MAXCOUNT
    19 }EventType;
    View Code

      对于自定义的事件类型可以通过xml中如下定义:

       <eventlist>
          <event name="X CT-COM BIND"></event>
       </eventlist>

      3)

     1 struct trf_param
     2 {
     3     char                name[PARAM_NAME_LEN+1];     //参数名
     4     int                 type;                       //参数类型 trf_datatype_e
     5     int                 writable;                   //是否可写。0:不可写,1:可写,如果object
     6                                                     //可以Add,则可写
     7     int                 max_instance;               //属于Object, 最大instance值,-1表示无限制
     8     int                 notification;               //属于Parameter,  0:off,1:passive,2:active
     9     unsigned char       noti_rw;                    //属于Parameter,  是否可以设置上报属性,0 不可以 1 可以
    10     unsigned long       acl;                        /*属于Parameter, access list */
    11     TRFGetParamValueFunc    getparamval_func;       //属于Parameter, 取得参数值函数
    12     TRFSetParamValueFunc    setparamval_func;       //属于Parameter, 设置参数值函数
    13     TRFAddObjectFunc        addobject_func;         //属于Object, AddObject
    14     TRFDelObjectFunc        delobject_func;         //属于Object, DeleteObject
    15     TRFRefreshFunc          refresh_func;           //属于Object, 刷新
    16     struct trf_param    *parent;                    //父节点
    17     struct trf_param    *child;                     //子节点
    18     struct trf_param    *nextSibling;               //兄弟节点
    19 };
    View Code

      定义树形结构的节点,每个节点拥有自己的属性和方法。

      4) 设备相关函数

     1 <devlib name="/usr/lib/libcwmp.so"/>                          Libary的位置
     2 <auth name="dev_get_auth"/>                              是否需要开启ACS的认证
     3 <listenport name="dev_get_listenport"/>                          CWMP的监听端口
     4 <wanparamname name="dev_get_wanparam_name"/>                  取得WAN口的参数项名称的全路径
     5 <bootstrap name="dev_bootstrap"/>                          判断是否取得首次连接到ACS的标志
     6 <init name="dev_init"/>                                  初始化设备操作
     7 <reboot name="dev_reboot"/>                              设备的reboot方法
     8 <download name="dev_download"/>                              设备的download的方法,包括下载,升级之类的方法
     9 <upload name="dev_upload"/>                              设备upload的方法,包括生成配置文件,上传日志等方法
    10 <cwmpenable name="dev_cwmp_enable"/>                          判断是否启动CWMP进程
    11 <urldnsresolve name="dev_url_dns_resolve"></urldnsresolve>    ACS的URL解析
    12 ...
    13 其他参数项相关的操作函数
    View Code

    五. 总结

      CWMP core与Datamodel分离,通过不断的调试和实践,并应用于不同的运营商(电信,联通,移动),使得CWMP core的程序不断成熟和稳定。

      对于相同的功能,我们仅仅需要修改device.xm就可以实现需求,而不用去修改代码;

      对于新增的参数项或者节点开发,我们仅需要开发设备相关的库;

      对于新增的事件或者ACS下发的任务,修改library的同时我们只需要稍微修改CWMP core的程序就可以达到目的;

      对于新增模块,我们采用线程"插入"的思想来实现,而不用修改程序的主体架构。

    六. 参考

    1. http://www.docin.com/p-1306443672.html

    2. http://www.easycwmp.org/

  • 相关阅读:
    禁用aspx页面的客户端缓存
    水晶报表的自动换行(转)
    ORACLE锁的管理
    同时使用有线和无线
    Oracle系统表的查询
    Oracle中临时表的深入研究
    我的My Life Rate
    [学习笔记]c#Primer中文版命名空间
    出差兰州·火车上
    [学习笔记]c#Primer中文版类设计、static成员、const和readonly数据成员
  • 原文地址:https://www.cnblogs.com/myblesh/p/6259765.html
Copyright © 2020-2023  润新知