聲明:本文涉及的開源程序代碼學習和研究,嚴禁用於商業目的。 如有任何問題,歡迎和我交流。(企鵝號: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);
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>
InternetGatewayDevice是整個參數樹的根。obj表示這是一個對象,obj可以讀,可以寫,當obj的name為0時,表示該obj可以是個模板,為創建后面的實例提供一個模板,當ACS查詢時,不會把obj name為0的Obj發送給ACS。Obj的rw=1,表示該obj可以添加子obj,通過addobj_func來添加,通過delobj_func來刪除,refresh_func表示刷新該obj下的信息。 Obj的noti_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
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 };
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;
對於自定義的事件類型可以通過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 };
定義樹形結構的節點,每個節點擁有自己的屬性和方法。
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 其他參數項相關的操作函數
五. 總結
CWMP core與Datamodel分離,通過不斷的調試和實踐,並應用於不同的運營商(電信,聯通,移動),使得CWMP core的程序不斷成熟和穩定。
對於相同的功能,我們僅僅需要修改device.xm就可以實現需求,而不用去修改代碼;
對於新增的參數項或者節點開發,我們僅需要開發設備相關的庫;
對於新增的事件或者ACS下發的任務,修改library的同時我們只需要稍微修改CWMP core的程序就可以達到目的;
對於新增模塊,我們采用線程"插入"的思想來實現,而不用修改程序的主體架構。
六. 參考
1. http://www.docin.com/p-1306443672.html