UPNP编程

发布时间:2016-12-11 2:34:14 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"UPNP编程",主要涉及到UPNP编程方面的内容,对于UPNP编程感兴趣的同学可以参考一下。

零、SDK的安装 upnp的概念就不理会了,网上很多,这里偏向于具体编程。 SDK使用upnp1.6.17版本,这是一个linux下的开源版本,目前仍然在维护,下载地址: http://pupnp.sourceforge.net/ 安装SDK相对比较简单,参考阅读SDK目录下的README 我使用命令如下: tar jxvf libupnp-1.6.17.tar.bz2 cd libupnp-1.6.17/ ./configure --prefix=/home/momo/DLNA --enable-sample make make install 这样在/home/momo/DLNA目录下就可以找到include和lib两个目录了,里面就是头文件和库,在upnp/sample目录下是示例程序。 交叉编译目前没有去理会,先学习下X86下面UPNP的编程。 另外可以在网上下载intel upnp tools来对自己编写的设备进行测试。 一、名词解释和XML文档 UDN:uuid,设备唯一名 SID:订阅标识,唯一 ServiceId:服务ID号,唯一 URI:Universal Resource Identifier UPNP编程分为设备端和客户端(控制点),设备端采用XML来提供自己的信息,主要包括自身描述XML文档和动作状态文档。 自身描述XML文档格式: 见文档。 动作状态XML文档格式: 见文档。 这些XML的编写,除了自己按照格式填之外,还可以使用Intel upnp tools中的DeviceBuild和ServiceAuthor生成。我测试DeviceBuild好像有点问题,无法保存。 其中自身描述XML文档有个节点presentationURL,这个主要用来表现设备的界面,是一个网址,网页是自己编写的。 二、XML的操作 编写设备或控制点都需要操作XML。 下面是一个普通的XML: <serviceList>  <service>   <serviceType>urn:schemas-upnp-org:service:service_0001:1</serviceType>   <serviceId>urn:upnp-org:serviceId:0001</serviceId>   <controlURL>/upnp/control/service_0001</controlURL>   <eventSubURL>/upnp/event/service_0001</eventSubURL>   <SCPDURL>/service_0001SCPD.xml</SCPDURL>  </service> </serviceList> 节点是XML中一个很重要的概念,对于理解后面操作XML有帮助,下面这些都是节点: <xxx> <yyy>xxxxxx</yyy> </xxx> <serviceType>urn:schemas-upnp-org:service:service_0001:1</serviceType> urn:schemas-upnp-org:service:service_0001:1 其中有<>的节点是Element节点,节点分类比较多,详情自己百度下。 1.API int UpnpDownloadXmlDoc(const char *url, IXML_Document **xmlDoc) 下载url指向的XML文档,保存在*xmlDoc中。 IXML_NodeList *ixmlDocument_getElementsByTagName( IXML_Document *doc, const DOMString tagName) 从一个XML文档中读取所有标签名是tagName的节点,并把它们编制成一个单向链表。 IXML_Node *ixmlNodeList_item( IXML_NodeList *nList, unsigned long index) 从链表中取出其中一个节点。 ixmlNode getChildNodes (IXML Node* nodeptr ) 取出nodeptr的子节点,组成一个节点链表,调用ixmlNodeList_item(child_node_list,1),可以得到: IXML_Node *ixmlNode_getFirstChild(IXML_Node *nodeptr) 取出一个节点的第一个子节点。 const DOMString ixmlNode_getNodeValue(IXML_Node *nodeptr) 取出一个节点的值。 void ixmlNodeList_free(IXML_NodeList *nList) 释放节点列表空间。 void ixmlDocument_free(IXML_Document *doc) 释放DOC空间 2.举例 对于之前那个XML,如何读取其中的ServiceId值呢?(假设XML为”./web/device_desc.xml”) IXML_Document *doc_desc; UpnpDownloadXmlDoc(”./web/device_desc.xml”,&doc_desc); IXML_NodeList *node_list=ixmlDocument_getElementsByTagName(doc_desc,”ServiceId”); IXML_Node *node= ixmlNodeList_item(node_list,0); node= ixmlNode_getFirstChild(node); char *service_id=strdup(ixmlNode_getNodeValue(node)); ixmlNodeList_free (node_list); ixmlDocument_free(doc_desc); …. free(service_id); 三、upnp设备的编写 一、设备的初始化 1.初始化SDK UpnpInit( ip_address, port ); 2.注册虚拟目录: char* web_dir_path="./web"; UpnpSetWebServerRootDir( web_dir_path ); 3.注册根设备 UpnpRegisterRootDevice( desc_doc_path, MyDeviceCallbackEventHandler, &device_handle, &device_handle ); 4.初始化服务和状态(这部分自己完成,非SDK里面的函数): SetupServiceAndVarible(desc_doc_path); 5.广播设备上线消息: unsigned int default_advr_expire=100; UpnpSendAdvertisement( device_handle, default_advr_expire); 6.阻塞主线程,等待设备退出信号,如果退出,调用: UpnpFinish(); 二、处理设备请求 设备广播之后就可以处理其他设备发送过来的请求了,请求是异步和并发的,所以要加锁。一个请求到来就会调用UpnpRegisterRootDevice中注册的回调函数(上面的MyDeviceCallbackEventHandler),定义如下: int MyDeviceCallbackEventHandler(Upnp_EventType EventType, void *Event, void *Cookie) EventType表示请求的类型,作为一个设备而言,它只需要处理三种请求: UPNP_EVENT_SUBSCRIPTION_REQUEST:订阅请求 UPNP_CONTROL_GET_VAR_REQUEST: 变量请求 UPNP_CONTROL_ACTION_REQUEST: 动作请求 Event保存请求信息的结构体 设备处理订阅请求: 1.将Event转换为订阅请求类型: (struct Upnp_Subscription_Request *)Event 2.从请求结构体中获取udn,service_id,sid const char *l_serviceId = NULL; const char *l_udn = NULL; const char *l_sid = NULL; l_serviceId = sr_event->ServiceId; l_udn = sr_event->UDN; l_sid = sr_event->Sid; 3.跟据service_id和udn查找设备提供的服务列表,如果有匹配项,那么接受订阅: UpnpAcceptSubscription(device_handle,l_udn,l_serviceId, (const char**)g_dev_service_list[i].VariableName, (const char**)g_dev_service_list[i].VariableStrVal, g_dev_service_list[i].VariableCount,l_sid); 处理动作请求: 1.将Event转换为动作请求类型: (struct Upnp_Action_Request *)Event 2.从请求结构体中获取udn,service_id,action_name: const char *dev_udn = NULL;     const char *service_id = NULL;     const char *action_name = NULL;     dev_udn     = ca_event->DevUDN;     service_id  = ca_event->ServiceID;     action_name = ca_event->ActionName; 3.跟据udn、service_id和action_name查找对应的action函数,如果找到了就调用这个action函数,action函数定义如下: typedef int (*upnp_action) (IXML_Document *request, IXML_Document **out, char **errorString); IXML_Document action_result; char *error_string; ret_code=g_dev_service_list[i].actions[j]( ca_event->ActionResult, &ca_event->ActionResult, &error_string, (void*)&g_dev_service_list[i] ); if(ret_code == UPNP_E_SUCCESS) ca_event->ErrCode=UPNP_E_SUCCESS; 如果没有发现匹配的action,那么返回401的错误代码: ca_event->ActionResult=NULL;     strcpy(ca_event->ErrStr, "Invalid Action" ); ca_event->ErrCode=401; 在action函数中,通常可能改变了服务状态变量的值,这时候要调用通知函数UpnpNotify: UpnpNotify( device_handle, pservice->UDN, pservice->ServiceId, ( const char ** )&pservice->VariableName[VAR_INDEX_POWER], ( const char ** )&pservice->VariableStrVal[VAR_INDEX_POWER], 1); action函数中处理完和设备的相关数据后,调用UpnpAddToActionResponse设置返回结果: if( UpnpAddToActionResponse( out,pservice->ActionNames[ACT_INDEX_POWERON], pservice->ServiceType, pservice->VariableName[VAR_INDEX_POWER], pservice->VariableStrVal[VAR_INDEX_POWER]) != UPNP_E_SUCCESS ) {             *out= NULL;             *errorString = "Internal Error";             return UPNP_E_INTERNAL_ERROR; } 处理变量请求: 1. 将Event转换为变量请求类型: (struct Upnp_State_Var_Request *)Event 2.获取udn,service_id和var_name:     dev_udn=cgv_event->DevUDN; service_id=cgv_event->ServiceID; var_name=cgv_event->StateVarName; 3.在服务列表中查找匹配项,如果找到就将变量值设置到Event中:     for(i=0;i<DEV_SERVICE_COUNT;i++){         if(!strcmp(g_dev_service_list[i].UDN,dev_udn) &&   !strcmp(g_dev_service_list[i].ServiceId,service_id)){             for(j=0;j<g_dev_service_list[i].VariableCount;j++){                 if(!strcmp(g_dev_service_list[i].VariableName[j],var_name)){                     cgv_event->CurrentVal = ixmlCloneDOMString( g_dev_service_list[i].VariableStrVal[j]);                 break;                 }             }             break;         }     } 4.设置好Event中的返回值: if(i==DEV_SERVICE_COUNT && j==g_dev_service_list[i].VariableCount){         cgv_event->ErrCode=404;         strcpy(cgv_event->ErrStr, "Invalid Variable" );     }else{         cgv_event->ErrCode=UPNP_E_SUCCESS;     } 四、编写UPNP控制点 一、控制点的流程: 1.初始化SDK库: int UpnpInit(const char *HostIP, unsigned short DestPort) 2.注册控制点: int UpnpRegisterClient( Upnp_FunPtr Fun, const void *Cookie, UpnpClient_Handle *Hnd) 3.发出搜索: int UpnpSearchAsync( UpnpClient_Handle Hnd, int Mx, const char *Target_const, const void *Cookie_const ) 4.处理各种事件 5.退出: UpnpUnRegisterClient(g_ctrl_handle); UpnpFinish(); 二、控制点处理的事件: 1. UPNP_DISCOVERY_SEARCH_RESULT和UPNP_DISCOVERY_ADVERTISEMENT_ALIVE 在调用UpnpSearchAsync发出搜索请求后,设备端收到请求会返回UPNP_DISCOVERY_SEARCH_RESULT,如果超时会得到UPNP_DISCOVERY_SEARCH_TIMEOUT。 设备端上线后会广播一次,这时控制点会收到UPNP_DISCOVERY_ADVERTISEMENT_ALIVE的消息。 收到这两个事件时,需要跟据事件中的URL将设备的XML文档下载过来,然后将设备添加到控制点维护的设备链表中,以供后期使用。 location = event->Location; err_code = UpnpDownloadXmlDoc(location, &desc_doc); if (err_code != UPNP_E_SUCCESS) { dprinterr("Error obtaining device description from %s -- error = %d", location, err_code); } else { add_device_to_list(desc_doc, location, event->Expires); } if( desc_doc ) { ixmlDocument_free(desc_doc); } 2. UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE 当设备下线时会广播此消息,控制点收到后,需要把设备从链表中移除。 int err_code = event->ErrCode; if (err_code != UPNP_E_SUCCESS) { dprinterr("Error in Discovery ByeBye Callback -- %d", err_code); } const char *udn = event->DeviceId; remove_device(udn); 3. UPNP_CONTROL_ACTION_COMPLETE 当控制点发送了ACTION后,会收到这个消息,它是用来返回ACTION执行的结果,控制点也可以通过这个判断ACTION是否执行成功,是否需要再次发送ACTION。 4. UPNP_CONTROL_GET_VAR_COMPLETE 当控制点发送获取设备状态后,会收到此消息,可以从消息中读取到状态。 5. UPNP_EVENT_RECEIVED 当控制点发送订阅服务,并订阅成功后,如果设备调用Notify,就会收到此消息,主要用于设备通知控制点状态发生变化。 三、控制点发出的请求 1.搜索请求 int UpnpSearchAsync( UpnpClient_Handle Hnd, int Mx,//超时时间,单位秒 const char *TTarget_constarget_const,//搜索匹配条件 const void *Cookie_const); 搜索匹配条件可以是以下: ssdp:all 搜索所有的设备和服务 upnp:rootdevice 只搜索根设备 uuid:device-UUID 搜索特定的设备 urn:schemas-upnp-org:device:deviceType:ver 搜索某一类型的设备 urn:schemas-upnp-org:service:serviceType:ver 搜索某一类型的服务 urn:domain-name:device:deviceType:ver  urn:domain-name:service:serviceType:ver  发出搜索请求后,如果此设备在网络上,就会返回UPNP_DISCOVERY_SEARCH_RESULT,如果不能及时返回,应用程序就会收到UPNP_DISCOVERY_SEARCH_TIMEOUT。 2.动作请求 找到设备以后,可以请求设备执行某项动作,这些动作可以是控制设备开关,也可以是返回设备状态(据说UPNP论坛推荐这样做,而不是使用请求状态变量来获取设备状态)。 在发送动作请求之前,需要创建一个动作: IXML_Document *UpnpMakeAction( const char *ActionName, const char *ServType, int NumArg, const char *Arg,...); int UpnpAddToAction( IXML_Document **ActionDoc, const char *ActionName, const char *ServType, const char *ArgName, const char *ArgVal); 创建好动作以后,就可以开始发送动作了: int UpnpSendActionAsync( UpnpClient_Handle Hnd, const char *ActionURL, const char *ServiceType, const char *DevUDN, IXML_Document *Action, Upnp_FunPtr Fun, const void *Cookie); typedef int (*Upnp_FunPtr)(Upnp_EventType EventType,void *Event, void *Cookie); 其中Upnp_FunPtr是回调函数,里面可以得到动作执行的结果。 示例: if( 0 == param_count ) {         action_node =UpnpMakeAction(actionname,service_type, 0,NULL );     } else {         for( param = 0; param < param_count; param++ ){        if( UpnpAddToAction(&action_node,actionname,service_type,param_name[param], param_val[param]) != UPNP_E_SUCCESS ) {                 dprinterr("ERROR:Trying to add action param"); ithread_mutex_unlock( &g_ctrl_mutex ); return -1;             }         }     }     ret_code = UpnpSendActionAsync(client_handle,ctrl_url, service_type,NULL,action_node,                               upnp_ctrl_event_handler,NULL); 3.设备状态请求 有两种方法可以获取到当前设备的状态,一种是使用动作请求,一种是设备状态请求,其中后面一种不推荐使用了。 int UpnpGetServiceVarStatus( UpnpClient_Handle Hnd, const char *ActionURL, const char *VarName, DOMString *StVarVal); int UpnpGetServiceVarStatusAsync( UpnpClient_Handle Hnd, const char *ActionURL, const char *VarName, Upnp_FunPtr Fun, const void *Cookie); 4.订阅请求 设备状态发生变化时,也可以主动调用Notify函数通知已订阅此状态的控制点。 控制点调用下面这个函数订阅服务的状态: int UpnpSubscribeAsync( UpnpClient_Handle Hnd, const char *PublisherUrl,//event_url int TimeOut, Upnp_FunPtr Fun, const void *Cookie); 取消订阅: int UpnpUnSubscribe( UpnpClient_Handle Hnd, const Upnp_SID SubsId); 五、数据的传输 可以使用HTTP进行数据传输: 1.从服务器获取数据: UpnpOpenHttpGet UpnpReadHttpGet UpnpCloseHttpGet 2.提交文件到服务器: UpnpOpenHttpPost UpnpWriteHttpPost UpnpCloseHttpPost 六、UPNP的标准服务 主要包括下面四个标准服务: 1. Content Directory Service: Enumerates the available content. 2. Connection Manager Service: Determines how the content can be transferred from the UPnP AV MediaServer to the UPnP AV MediaRenderer devices. 3. AV Transport Service: Controls the flow of the content. 4. Rendering Control Service: Controls how the content is played.

上一篇:WinCE6.0 KITL概要
下一篇:c语言指针的学习(指针作为函数的参数)

相关文章

关键词: UPNP编程

相关评论