libgupnp源码解读之对象框架与设备发现

来源:互联网 发布:2016开淘宝店 编辑:程序博客网 时间:2024/05/24 06:20

    libgupnp是一款开源的upnp软件。它采用gobject思想设计,实现了异步消息传递;用面向过程语言C实现了面向对象的框架。当然,最主要的是,它实现了通用即插即用协议UPNP,方便了很多数字家庭网络的应用开发者。

     接下来,我先简单介绍下UPNP协议;接着从libgupnp的类设计结构图着手,以给大家提供框架上的认识;然后,我将分析upnp的设备发现部分在libgupnp中的设计与实现。

 一、了解UPNP

         UPnP 网络的基本架构为设备、服务和控制点。

         设备:指其他服务或者是设备的容器,一个设备可以包含 其它的逻辑设备。

         服务:服务指一个逻辑功能单位,服务代表动作和使用状  态变量的物理设备的部分或所有状态

          控制点:也就是一个控制器,它可以检索设备和服务描述,发送动作到服务,查询服务的状态变量和从服务接收事件,允许用户使用或动行一个设备,例如CD播放机的程序可以认为是控制点。

              UPnP定义了设备之间、设备和控制点、控制点之间通讯的协议。完整的UPnP由设备寻址、设备发现、设备描述、设备控制、事件通知和基于Html的描述界面几部分构成。UPNP的协议栈如下图所示:

 二、GUPNP的对象布局

        gupnp的主要类图继承关系,简单表示如下:

1.GObject
   +----GSSDPClient
         +----GUPnPContext

2.
  GObject
   +----GSSDPResourceBrowser  (它聚合gssdpclient,因此具有网络设备发现功能)
         +----GUPnPControlPoint (  聚合GUPnPResourceFactory)
                    +----DLNADmp (这个对象是我为了方便说明,实现的一个对象。libgupnp中无此对象)
3.
  GObject
   +----GUPnPServiceInfo(它聚合gupnpcontext)
         +----GUPnPServiceProxy
4.
  GObject
   +----GUPnPDeviceInfo
         +----GUPnPDeviceProxy

5.
  GObject
   +----GUPnPDeviceInfo
         +----GUPnPDevice
               +----GUPnPRootDevice

    读到这里时,没必要关注太多细节。读者只需要知道这些对象之间存在的继承关系,以及这些对象与UPNP的元素架构及UPNP协议栈元素之间的对应关系即可。接下来,我将具体分析源码。

三、设备发现在LIBGUPNP中的实现

     为了方便说明,我将自己添加一个控制点dlnadmp实现。该DLNADMP是一种控制点,是GUPNPCONTROLPOINT的子类。

     首先,我们要创建一个dlnadmp对象,创建gmainloop对象,gmaincontext对象,以及创建一个DLNA线程,并初始化消息循环,启动mainloop。

创建dlna对象的代码示例如下:  

    dlna_dmp_new(context, "urn:schemas-upnp-org:device:MediaServer:1");

DLNADmp* dlna_dmp_new(GUPnPContext* context, const char* target)
{
 DLNADmp * self;
 
 g_return_val_if_fail(GUPNP_IS_CONTEXT(context), NULL);
 g_return_val_if_fail(target != NULL, NULL);

 self = g_object_new(DLNA_TYPE_DMP, "client",context,"target",target,NULL);---/*创建一个dlnadmp对象,并将其属性client指针成员赋初值为context。client的定义在其父类即gssdpreourcebrowser的成员gssdpclient *client。注意context实际上是ssdpclient的子类。

gssdpclienr return self;
}

    其次,在发送搜寻设备的请求前,dlnadmp(从指点)需要做如下几个工作:

   (1)设置请求socket回调函数。

(2)安装一些信号回调函数:

(3)信号回调的连接

    用户在新建一个控制点时,会设置一些参数。如ST(搜寻目标)等,代码示例如下:

                g_object_new(DLNA_TYPE_DMP, "client",context,"target",target,NULL);

我们先来看看GSSDPResourceBrowser 的定义:

根据如下的类图关系,我们知道,代码g_object_new(DLNA_TYPE_DMP, "client",context,"target",target,NULL);

实际上对对控制点client成员进行了赋值和target成员进行了赋值.

  GObject

   +----GSSDPResourceBrowser  (它聚合gssdpclient,因此具有网络功能)

         +----GUPnPControlPoint ( 聚合GUPnPResourceFactory)

                                +----DLNADmp

那么,对client成员赋值是如何进行的呢?(注意,成员变量client并不是一个字符串变量)。答案是gssdp_resource_browser_set_property

 

gssdp_resource_browser_set_client函数中,将会完成信号与回调的连接:

 

    上面的代码完成了什么工作呢?当然,是一些重要属性的初始化,为设备发现作好准备。一是,成功设置了ssdoclient,即设备发现客户端;二是,成功设置了ST,即设备发现目标。

    同时,该代码还触发了如下动作:安装几个信号处理器。    signals[RESOURCE_AVAILABLE] = g_signal_new ("resource-available"........ signals[RESOURCE_UNAVAILABLE] =
                g_signal_new ("resource-unavailable",........

  同时ssdpclient在初始化时会安装响应callback信号signals[MESSAGE_RECEIVED] =
                g_signal_new ("message-received",。。。。。。。

在给DLNAdmp 的client赋初值context时,由于在初始化时安装好了属性

g_object_class_install_property
                (object_class,
                 PROP_CLIENT,
                 g_param_spec_object
                         ("client",
                          "Client",
                          "The associated client.",
                          GSSDP_TYPE_CLIENT,
                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
                          G_PARAM_STATIC_BLURB));

于是将调用gssdp_resource_browser_set_property函数,进而 将连接信号与闭包。 

      既然一些准备工作做好了,接下来我们就可以开始启动设备发现这一功能了。如何启动呢,调用函数gssdp_resource_browser_set_active (GSSDPResourceBrowser *resource_browser, gboolean              active)即可。该函数主要作了如下工作:发送设备discovery请求;同时,修改自己的active属性g_object_notify (G_OBJECT(resource_browser), "active");

    调用顺序:_gssdp_client_send_message-----------start_discovery (resource_browser);

      在调用start_discovery以后,用户就可以依靠之前安装的套接字的回调request_socket_source_cb和multicast_socket_source_cb来获取网络上得信息。

。这2个回调最终都将调用socket_source_cb (client->priv->multicast_socket, client);。在socket_source_cb中,将会调用parse_http_request对接收到的内容进行解析,如果解析成功,就发送相关信号通知ssdpclient点消息已经收到:   

      Message_received_cb函数会处理将调用   received_announcement (resource_browser, headers);

  /* Check announcement type */
        if      (strncmp (header,
                          SSDP_ALIVE_NTS,
                          strlen (SSDP_ALIVE_NTS)) == 0)
                resource_available (resource_browser, headers);
        else if (strncmp (header,
                          SSDP_BYEBYE_NTS,
                          strlen (SSDP_BYEBYE_NTS)) == 0)
                resource_unavailable (resource_browser, headers);

如果是alive消息,调用 resource_availbale函数,

可以在代码中看到,Resource_available函数在gupnp-resource-browser.c文件中有一个。那么是否是调用它呢?我认为不是。它应该是被覆盖了,最后调用的是位于gupnp_control_point.c里面的gupnp_control_point_resource_available函数。这里相当于实现了C++里的虚函数机制--多态:

gupnp_control_point.c里的 gupnp_control_point_resource_unavailable最后又调用load_description (control_point,
                          locations->data,
                          udn,
                          service_type);
函数,用以加载或解析设备URL。当设备描述文档下载完毕后,会调用description_loaded函数。description_loaded函数会调用

process_device_list去递归的搜索匹配的设备,并且会发送信号:

由于dlna_dmp_instance_init(DLNADmp * self)函数时,我们已经设定:

static void dlna_dmp_instance_init(DLNADmp * self)
{
 self->priv = DLNA_DMP_GET_PRIVATE(self);
 self->priv->dev_list = NULL;
 self->priv->dev_list_lock = g_mutex_new();
 self->priv->didl_parser = gupnp_didl_lite_parser_new();
 self->priv->curDevInfo = NULL;
 self->priv->browse_data = NULL;
 self->priv->cancelled = FALSE;
 self->priv->cancel_mutex = g_mutex_new();
 self->priv->cancel_cond = g_cond_new();
 
 }

所以调用:

static void device_proxy_available_cb (GUPnPControlPoint *cp,
                               GUPnPDeviceProxy  *proxy,gpointer user_data)
{
    const char *type;
 DLNADmp* self = user_data;

    type = gupnp_device_info_get_device_type(GUPNP_DEVICE_INFO (proxy));

 if(g_pattern_match_simple(MEDIA_SERVER, type))
 {
        add_media_server(self,proxy);
    }
}

(*dmp_event_callbackDLNA_EVENT_DEVICE_ADDED_,gupnp_device_info_get_udn((GUPnPDeviceInfo*)proxy));

device_proxy_available_cb中,用户可以把得到的server信息添加到缓存中,并通知用户发现了一个设备。

 

 

原创粉丝点击