Device 分析

 

Device 分析


1. Device 初始化

Device.java

传递描述文件全路径给构造函数 --> 解析出的根节点是 rootNode,子节点(Device)是 deviceNode --> 将描述文件的全路径保存在 DeviceData 类中。

 

2. Device 启动

调用stop() --> 创建并启动 httpServerList --> 创建并启动 ssdpSearchSockList --> 发送上线广播 --> 启动 Advertiser。

===》

    2.1 创建并启动 httpServerList 分析:

httpServerList.open() --> 根据 InetAddress[] 得到 String[] bindAddress --> 用每个地址和 port 创建并 opne 一个 HTTPServer。

如果创建失败,则上一层的调用会自动 port++ 然后重新开始 创建并启动httpServerList。

如果成功:httpServerList.addRequestListener(this) --> httpServerList.start ()  --> 调用每一个 HTTPServer.start()。

HTTPServer 分析:

在上一层指定的地址上创建 ServerSocket 并监听 --> 收到新的连接后创建 HTTPServerThread 并 start 。

HTTPServerThread 分析:

创建 httpRequest 并传入 socket --> 在 while 中 read() --> 执行 httpServer.performRequestListener(httpReq) --> 根据 httpReq.isKeepAlive() 决定是否退出循环。

 

总结:根据指定的 InetAddress[] 和 port 创建监听连接的HTTP监听线程,有连接进入后创建通信线程,以 httpRequest 的格式收取数据,并发给 performRequestListener 外理。而这个接口是 httpServerList.addRequestListener(this)  指定的 this(Device,实现了这个接口)。请求的处理这里先不讨论。

 

    2.2 创建并启动 ssdpSearchSockList 分析

ssdpSearchSockList.open() --> 根据 InetAddress[],ssdpPort 和 IPV4/IPV6 的多播地址创建 SSDPSearchSocketList --> ssdpSearchSockList.open() --> 根据 InetAddress[] 得到 String[] 地址列表 --> 依次根据 addr port IPV4/IPV6 多播地址创建 SSDPSearchSocket -> 创建 ssdpMultiSock  --> ssdpSearchSockList.addSearchListener(this) --> ssdpSearchSockList.start() --> 每一个 SSDPSearchSocket start --> 在 while 中调用 SSDPPacket packet = receive() 接收多播地址上发来的数据包 --> recvPacket.setTimeStamp(System.currentTimeMillis()) --> 判断如果是 SEARCH 包就调用 performSearchListener(packet)。

 

总结:创建线程监听网络上的搜索消息,然后执行 performSearchListener() 处理,(Device 实现了这个接口)。

 

    2.3  发送上线广播(announce)分析

得到 InetAddress[] binds = getDeviceData().getHTTPBindAddress() 然后变成 String[] --> 对每个地址调用 announce(String bindAddr) --> 根据 bindAddr 创建 devLocation的URL --> 根据 bindAddr 创建 SSDPNotifySocket --> 针对 rootDevice, device, service 分别发送对就的通知。 

 

总结:在通知中含有根据 getDeviceData().getHTTPBindAddress()  得到的本地 HTTP 监听地址和端口而创建的标头项,所以控制点收到通知后如果想要了解设备,就可以向这个地址发送 GET 请求,就能得到设备的描述文件。可以看出这个 GET 请求的接收者是 2.1 中创建的监听线程。

 

    2.4 Advertiser 分析

    根据过期时间自动发送上线通知,保持在线状态。

 

到这里为止可以看出 Device 的初始化所做的工作了:

解析设备描述文件得到 rootNode 和 deviceNode 然后把描述文件的地址保存;

启动线程用来监听所有 TCP 消息,接收后执行接口方法处理消息;

启动线程监听多播地址的 MUDP 消息,如果是 Search 消息就执行接口方法处理消息;

向多播地址发送上线通知,通知里有很多内容,其中包括描述文件的地址和 TCP 监听线程的地址,这样对此设备感兴趣的控制点就可以连接这个地址获得更多的信息。

启动一个自动发送在线广播的线程。

 

3. 事件处理 (从中可以了解到 Device 怎么处理 Service, DeviceList,因为前面并没有明显地保存来自描述文件的这些信息)

就按照事件发生的顺序来分析:

  3.1 对 SEARCH 事件的处理。

  控制点上线后会向网络广播地址发送 SEARCH 消息表明自已感兴趣的设备,Device 的 2.2 收到消息后就开始处理。

  deviceSearchReceived(SSDPPacket ssdpPacket) --> 立即调用 deviceSearchResponse(ssdpPacket) --> ssdpPacket.getST() 先得到 ST (搜索目标) -->

      3.1.1  判断是否 ST.isAllDevice(ssdpST) 如果控制点对所有设备都感兴趣 --> getNotifyDeviceNT() 得到 UDN ,这个是写在描述文件里的 --> postSearchResponse(ssdpPacket, devNT, devUSN);  发送回应给控制点。

      看下 postSearchResponse 是怎么实现的。

      ssdpPacket.getLocalAddress() 这样就得到了收到这个包的本地地址信息 --> rootDev.getLocationURL(localAddr) 这样得到了本地 TCP 监听的地址信息 --> 用 ssdpPacket.getRemoteAddress() 和  ssdpPacket.getRemotePort() 得到控制点的地址信息 --> 然后就是构造数据包,根据控制点的地址信息发送 UDP 数据包。

      从这里看出控制点发头 Search 这个 MUDP 消息的地址也用来接收 UDP 信息。所以控制点是用绑定了指定的本地地址的 SOCKET 发送的消息,同时还设置了监听线程监听这个套接字的收到的消息。

      3.1.2 其它的判断也是对 ST 进行判断是否找的是符合自已,如果是的就发送自已的信息如同上面。

      3.1.3 getServiceList() 这下轮到服务了。仔细看一下这里的过程。

            3.1.3.1 先 new ServiceList() 其实是一个定制的 Vector --> getDeviceNode().getNode(ServiceList.ELEM_NAME) 找到了描述文件中的 serviceList 节点 --> 遍历每一个 service 节点 并用 new Service(node) 创建 Service 类,然后加入 serviceList 中。

            3.1.3.2 遍历 serviceList,调用每一个 service.serviceSearchResponse(ssdpPacket) --> 这个方法也和 Device 里的处理类似,判断自已是否符合 ST 决定是否发送自已的信息的数据包。

       3.1.4 最后是 deviceList 的处理,没什么新内容。

    总结: 对 Search 事件的处理,首先是根据 ST 判断是否找的是自已,然后构造写有数据的回复包(内容参考UPNP文档),然后根据 Search 数据包得到本地地址和控制点的地址,向控制点的地址发送 UDP 数据包。这个过程中在处理 Service 时创建了 serviceList (service),但仍然没有解析服务的描述文件。

 

    3.2 对 HTTP 请求的处理

    Device 收到 HTTP 请求有以下几种情况:1.控制点请求描述文件;2.控制点调用服务的动作;3.控制点查询服务的状态;4.控制点请求多媒体资源;5.控制点订阅、续订、取消订阅。

        3.2.1 如果是 httpReq.isGetRequest() 或 httpReq.isHeadRequest() --> httpGetRequestRecieved(httpReq) 这个调用是根据请求的 URI 判断请求的是什么描述文件(根设备,某个服务,还是某个内嵌设备),然后将对应描述文件的内容写在数据包里发送回控制点。具体实现是:

            httpGetRequestRecieved() 的实现:httpReq.getURI() 得到请求的URI -->

            3.2.1.1 如果是 isDescriptionURI(uri) --> httpReq.getLocalAddress() 得到本地地址(源码的这里算是无用功了,因为这个地址在下一句中另外得到) --> getDescriptionData(localAddr) 返回的是描述信息的 byte[] 。

                3.2.1.1.1 看看 getDescriptionData(String host) 做了什么。

                 updateURLBase(host) 在 rootNode 中添加了 urlBase 节点,这就是描述文件不写这个子元素的原因,这里会添加 --> getRootNode() 然后加上一点格式,然后以 String 的格式输出,然后转成 byte[] 就好了,描述文件的内容已经产生。

           3.2.1.2 如果是 getDeviceByDescriptionURI(uri) 说明请求的是某个内嵌设备的描述文件 --> 一样的处理,得到本地地址,得到描述文件,返回描述文件的内容。

           3.2.1.3 如果是 getServiceBySCPDURL(uri) 说明请求的是某个服务的 SCPD 文件(暂时管它叫描述文件) --> embService.getSCPDData() --> getSCPDNode()。

               3.2.1.3.1 仔细分析一下 getSCPDNode()。 getServiceData() --> data.getSCPDNode() --> 如果得到 node 就可以直接返回了,否则继续下面的处理 --> getDevice() --> getSCPDURL() --> rootDev.getDescriptionFilePath() --> new File(rootDevPath.concat(scpdURLStr)) 这是根据路径+文件名得到的服务的 SCPD 文件 --> getSCPDNode(f) --> parser.parse(scpdFile) 解析 XML 文件得到 SCPD 节点 --> 如果得到了节点就设置一下然后返回这个节点 data.setSCPDNode(scpdNode),否则继续下面的步骤 -->

new URL(rootDev.getAbsoluteURL(scpdURLStr)) 这是用另一种方式创建 SCPD 文件的路径 --> getSCPDNode(scpdUrl) 与上面重演一遍,如果仍失败,进行下一步 --> 这一步又用一种新的方式创建了一条路径,再重复,如果还失败就放弃了,返回 null。这一步有一 BUG ,创建节点成功的话要把它返回,但源码没有返回。所以修改一下。

              吐槽:为什么要用这么多方式尝试,直接规定怎么做不就行了嘛,弄的这么麻烦~~

    3.2.2 如果是 httpReq.isPostRequest() --> httpPostRequestRecieved(httpReq) 发来这种请求一定是 Action 调用来了 --> 如果是 Action 调用的话就 soapActionRecieved(httpReq) --> soapReq.getURI() 每一个 Action 都是在某个服务里的,用这个 URI 可以知道是哪个服务里的 Action --> getServiceByControlURL(uri) 找到 Action 对就的服务了 --> 找服务的方法是 先遍历根设备的所有服务找到 URI 相同的服务就返回,如果没找到再遍历内嵌设备的服务,找到就返回 --> new ActionRequest(soapReq) 根据请求创建一个 ActionRequest 类。看看这个类怎么创建的。

        3.2.2.1 ActionRequest(HTTPRequest httpReq) --> set(httpReq) --> set((HTTPPacket)httpReq) + setSocket(httpReq.getSocket())。

            3.2.2.1.1 先看看 set((HTTPPacket)httpReq) 做了什么。setFirstLine(httpPacket.getFirstLine()) --> clearHeaders() 然后把所有 headers 复制过去 --> setContent(httpPacket.getContent()) 把内容也复制过去。

            3.2.2.1.2 setSocket(httpReq.getSocket()) 做了什么呢。 就是把 socket 保存了下来,接下来就可以用这个 socket 给控制点回复了!

       创建了 ActionRequest 以后,deviceControlRequestRecieved(crlReq, ctlService),看看这个方法做了什么。

       3.2.2.2 deviceControlRequestRecieved(ControlRequest ctlReq, Service service) --> 这里有两种情况,分别是查询和功能调用,做一个判断是哪种后分别调用处理函数如下:

            3.2.2.2.1 deviceQueryControlRecieved(new QueryRequest(ctlReq), service) --> ctlReq.getVarName() 得到变量的名字 --> service.hasStateVariable(varName) 到服务里找找有没有这个变量,没有就返回出错,有就继续 --> getStateVariable(varName) 得到 StateVariable,关注一下它怎么实现的 --> getStateVariable(null, name) 里面这么一个调用 --> getServiceList() 先得到服务列表 --> 用遍历的方法调用 service.getStateVariable(name) 得到就返回 没得到再到内嵌设备里去找 再看看这个 StateVariable 怎么得来的 --> getServiceStateTable() 得到服务的ServiceStateTable 切入看下它怎么得到 --> new ServiceStateTable() --> getSCPDNode().getNode(ServiceStateTable.ELEM_NAME) 得到 stateTable 节点 --> getServiceNode() 也得到服务节点 --> 遍历每一个节点 stateTableNode.getNode(n) 并 new StateVariable(serviceNode, node) --> stateTable.add(serviceVar) 加入进去。-----> 现在回到前面,已经找到了查询的值对就的 StateVariable --> stateVar.performQueryListener(ctlReq) 在这里处理 --> 看下是怎么处理的。

               3.2.2.2.1.1 performQueryListener(QueryRequest queryReq) --> getQueryListener() --> getStateVariableData().getQueryListener() --> 这个queryListener 在源码里没有设置,所以默认这个查询是不会有效果的。。如果需要,可以自已实现接口然后把它添加进去。添加的接口也在这个类里可以找到。

            3.2.2.2.2 deviceActionControlRecieved(new ActionRequest(ctlReq), service) --> ctlReq.getActionName() 得到动作的名字 --> service.getAction(actionName) 根据名字创建对就的 Action 类,看一下它怎么创建的。

                3.2.2.2.2.1 getAction(String actionName) --> getActionList() --> new ActionList() 先创建一个列表 --> getSCPDNode() 得到 SCPDNode --> scdpNode.getNode(ActionList.ELEM_NAME) 得到 ActionList 节点 --> actionListNode.getNode(n) 然后 new Action(serviceNode, node) 再  actionList.add(action) 就得到列表了 --> 遍历列表直到 name.equals(actionName) 找到 Action 才返回。

           继续刚才的进度 --> action.getArgumentList() 得到参数列表 --> ctlReq.getArgumentList() 得到控制点传来的 Action 的参数列表 --> actionArgList.setReqArgs(reqArgList) 把传进来的参数传递到本地的参数列表 --> action.performActionListener(ctlReq) 最后执行处理,看下怎么处理的。

              3.2.2.2.2.2 (ActionListener)getActionListener() 找到处理者 --> new ActionResponse() 创建一个回复对像 --> clearOutputAgumentValues() 清空输出参数 --> listener.actionControlReceived(this) 开始处理 如果处理成功就 actionRes.setResponse(this) 否则 设置返回的结果是出错 --> actionReq.post(actionRes) 把结果发回去。下面分别仔细看一下这里的几个处理的细节。

                    *******1 (ActionListener)getActionListener() 这个 listener 在源码里也是没有设置的,所以要由具体的实现者来设置。

                    *******2 最后发回结果时,用的 socket 就是一开始就保存下来的。

 

注:在一个DMS的例子里,ActionListener, QueryListener 这两个监听者都是自已实现了功能,然后添加进去的。因为具体的功能怎么做要作者有自已的方式。

其它的处理就不分析了:订阅,取消订阅等,就是用上面的接口加上实现的方法。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页