DLNA设备控制分析

目录

1,概述

2,设备控制之control pointer视角

2.1 查找render提供的服务

2.2 发送控制指令

3,设备控制之render视角

3.1 registerServlet网络服务注册

3.2 消息解析


1,概述

        DLNA设备、服务的注册及发现(依赖开源库cling),DLNA中设备的注册、发现主要基于UPNP协议实现,这是微软推行的一个标准。Upnp最大的愿景是希望任何设备只要一接入网络,所有网上的设备马上就能知道有新设备加入,这些设备之间就可以彼此通信。

2,设备控制之control pointer视角

2.1 查找render提供的服务

        由render注册流程可知,render设备提供三类服务ConnectionManagerService,AVTransportService,AudioRenderingControl。先获取服务后,再调用对应服务通过upnp协议控制render设备。

Service service = device.findService(serviceType);

这段代码 返回的是 服务 Service,serviceType 就是服务类型,比如 AVTransport ...

2.2 发送控制指令

下面就是控制设备播放的操作:

Service avtService = device.findService(new UDAServiceType("AVTransport"));
ControlPoint controlPoint = UpnpService.getControlPoint();
controlPoint.execute(new Play(avtService) {

            @Override
            public void success(ActionInvocation invocation) {
                super.success(invocation);
                // to do success
            }

            @Override
            public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
                // to do failure
                }
            }
        });

可见,controlPoint.execute(new Play(avtService) 这一句很关键。
它告诉我们

  1. ControlPoint 有一个 execute 方法
  2. 执行命令时 传入了一个 Play ,Play(服务)

在分析 发现设备 源码的时候,我们得出 controlPoint.execute(..) 是通过 ExecutorService.submit(...) 执行的。 最后的执行者是 ClingExecutor。
我们复习一下,来看看 ClingExecutor:

 public static class ClingExecutor extends ThreadPoolExecutor {

        public ClingExecutor() {
            this(new ClingThreadFactory(),
                 new ThreadPoolExecutor.DiscardPolicy() {
                     // The pool is unbounded but rejections will happen during shutdown
                     @Override
                     public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
                         // Log and discard
                         log.info("Thread pool rejected execution of " + runnable.getClass());
                         super.rejectedExecution(runnable, threadPoolExecutor);
                     }
                 }
            );
        }

        public ClingExecutor(ThreadFactory threadFactory, RejectedExecutionHandler rejectedHandler) {
            // This is the same as Executors.newCachedThreadPool
            super(0,
                  Integer.MAX_VALUE,
                  60L,
                  TimeUnit.SECONDS,
                  new SynchronousQueue<Runnable>(),
                  threadFactory,
                  rejectedHandler
            );
        }

        @Override
        protected void afterExecute(Runnable runnable, Throwable throwable) {
            super.afterExecute(runnable, throwable);
            if (throwable != null) {
                Throwable cause = Exceptions.unwrap(throwable);
                if (cause instanceof InterruptedException) {
                    // Ignore this, might happen when we shutdownNow() the executor. We can't
                    // log at this point as the logging system might be stopped already (e.g.
                    // if it's a CDI component).
                    return;
                }
                // Log only
                log.warning("Thread terminated " + runnable + " abruptly with exception: " + throwable);
                log.warning("Root cause: " + cause);
            }
        }
    }

可见 ClingExecutor extends ThreadPoolExecutor。
而 ThreadPoolExecutor 也是继承于 ExecutorService。
那么 最后执行的就是 ClingExecutor.submit(Runnable)。
submit 里面的参数类型是 Runnable。那么我们看看这个 Play 是不是一个 Runnable.

我们看一下这个 Play:

public abstract class Play extends ActionCallback {

    private static Logger log = Logger.getLogger(Play.class.getName());

    public Play(Service service) {
        this(new UnsignedIntegerFourBytes(0), service, "1");
    }

    public Play(Service service, String speed) {
        this(new UnsignedIntegerFourBytes(0), service, speed);
    }

    public Play(UnsignedIntegerFourBytes instanceId, Service service) {
        this(instanceId, service, "1");
    }

    public Play(UnsignedIntegerFourBytes instanceId, Service service, String speed) {
        super(new ActionInvocation(service.getAction("Play")));
        getActionInvocation().setInput("InstanceID", instanceId);
        getActionInvocation().setInput("Speed", speed);
    }

    @Override
    public void success(ActionInvocation invocation) {
        log.fine("Execution successful");
    }
}

指令通过下面代码发送,本质是通过router来发送控制指令,

protected StreamResponseMessage sendRemoteRequest(OutgoingActionRequestMessage requestMessage)
        throws ActionException, RouterException {

        try {
            log.fine("Writing SOAP request body of: " + requestMessage);
            getUpnpService().getConfiguration().getSoapActionProcessor().writeBody(requestMessage, actionInvocation);

            log.fine("Sending SOAP body of message as stream to remote device");
            return getUpnpService().getRouter().send(requestMessage);
        } catch (RouterException ex) {
            Throwable cause = Exceptions.unwrap(ex);
            if (cause instanceof InterruptedException) {
                if (log.isLoggable(Level.FINE)) {
                    log.fine("Sending action request message was interrupted: " + cause);
                }
                throw new ActionCancelledException((InterruptedException)cause);
            }
            throw ex;
        } catch (UnsupportedDataException ex) {
            if (log.isLoggable(Level.FINE)) {
                log.fine("Error writing SOAP body: " + ex);
                log.log(Level.FINE, "Exception root cause: ", Exceptions.unwrap(ex));
            }
            throw new ActionException(ErrorCode.ACTION_FAILED, "Error writing request message. " + ex.getMessage());
        }
    }

3,设备控制之render视角

3.1 registerServlet网络服务注册

        upnp初始化流程中,通过下面代码注册servlet服务,

    synchronized public void init(InetAddress bindAddress, final Router router) throws InitializationException {
        try {
            if (log.isLoggable(Level.FINE))
                log.fine("Setting executor service on servlet container adapter");
            getConfiguration().getServletContainerAdapter().setExecutorService(
                router.getConfiguration().getStreamServerExecutorService()
            );

            if (log.isLoggable(Level.FINE))
                log.fine("Adding connector: " + bindAddress + ":" + getConfiguration().getListenPort());
            hostAddress = bindAddress.getHostAddress();
            localPort = getConfiguration().getServletContainerAdapter().addConnector(
                hostAddress,
                getConfiguration().getListenPort()
            );

            String contextPath = router.getConfiguration().getNamespace().getBasePath().getPath();
            getConfiguration().getServletContainerAdapter().registerServlet(contextPath, createServlet(router));

        } catch (Exception ex) {
            throw new InitializationException("Could not initialize " + getClass().getSimpleName() + ": " + ex.toString(), ex);
        }
    }

关键句getConfiguration().getServletContainerAdapter().registerServlet(contextPath, createServlet(router)),监听网络消息,当有消息过来时,会回调createServlet(router)。

    protected Servlet createServlet(final Router router) {
        return new HttpServlet() {
            @Override
            protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

            	final long startTime = System.currentTimeMillis();
            	final int counter = mCounter++;
                if (log.isLoggable(Level.FINE))
                	log.fine(String.format("HttpServlet.service(): id: %3d, request URI: %s", counter, req.getRequestURI()));

                AsyncContext async = req.startAsync();
                async.setTimeout(getConfiguration().getAsyncTimeoutSeconds()*1000);

                async.addListener(new AsyncListener() {

                    @Override
                    public void onTimeout(AsyncEvent arg0) throws IOException {
                        long duration = System.currentTimeMillis() - startTime;
                        if (log.isLoggable(Level.FINE))
                            log.fine(String.format("AsyncListener.onTimeout(): id: %3d, duration: %,4d, request: %s", counter, duration, arg0.getSuppliedRequest()));
                    }


                    @Override
                    public void onStartAsync(AsyncEvent arg0) throws IOException {
                        if (log.isLoggable(Level.FINE))
                            log.fine(String.format("AsyncListener.onStartAsync(): id: %3d, request: %s", counter, arg0.getSuppliedRequest()));
                    }


                    @Override
                    public void onError(AsyncEvent arg0) throws IOException {
                        long duration = System.currentTimeMillis() - startTime;
                        if (log.isLoggable(Level.FINE))
                            log.fine(String.format("AsyncListener.onError(): id: %3d, duration: %,4d, response: %s", counter, duration, arg0.getSuppliedResponse()));
                    }


                    @Override
                    public void onComplete(AsyncEvent arg0) throws IOException {
                        long duration = System.currentTimeMillis() - startTime;
                        if (log.isLoggable(Level.FINE))
                            log.fine(String.format("AsyncListener.onComplete(): id: %3d, duration: %,4d, response: %s", counter, duration, arg0.getSuppliedResponse()));
                    }

                });

                AsyncServletUpnpStream stream =
                    new AsyncServletUpnpStream(router.getProtocolFactory(), async, req) {
                        @Override
                        protected Connection createConnection() {
                            return new AsyncServletConnection(getRequest());
                        }
                    };

                router.received(stream);
            }
        };
    }

其中,关键句router.received(stream),用于接收upnp网络控制消息,

    public void received(UpnpStream stream) {
        if (!enabled) {
            log.fine("Router disabled, ignoring incoming: " + stream);
            return;
        }
        log.fine("Received synchronous stream: " + stream);
        getConfiguration().getSyncProtocolExecutorService().execute(stream);
    }

走到AsyncServletUpnpStream#run(),

    public void run() {
        try {
            StreamRequestMessage requestMessage = readRequestMessage();
            if (log.isLoggable(Level.FINER))
                log.finer("Processing new request message: " + requestMessage);

            responseMessage = process(requestMessage);

            if (responseMessage != null) {
                if (log.isLoggable(Level.FINER))
                    log.finer("Preparing HTTP response message: " + responseMessage);
                writeResponseMessage(responseMessage);
            } else {
                // If it's null, it's 404
                if (log.isLoggable(Level.FINER))
                    log.finer("Sending HTTP response status: " + HttpURLConnection.HTTP_NOT_FOUND);
                getResponse().setStatus(HttpServletResponse.SC_NOT_FOUND);
            }

        } catch (Throwable t) {
            log.info("Exception occurred during UPnP stream processing: " + t);
            if (log.isLoggable(Level.FINER)) {
                log.log(Level.FINER, "Cause: " + Exceptions.unwrap(t), Exceptions.unwrap(t));
            }
            if (!getResponse().isCommitted()) {
                log.finer("Response hasn't been committed, returning INTERNAL SERVER ERROR to client");
                getResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } else {
                log.info("Could not return INTERNAL SERVER ERROR to client, response was already committed");
            }
            responseException(t);
        } finally {
            complete();
        }
    }

先处理消息responseMessage = process(requestMessage),处理完成后给个握手信息。

3.2 消息解析

writeResponseMessage(responseMessage),重点看process(requestMessage),
    public StreamResponseMessage process(StreamRequestMessage requestMsg) {
        log.fine("Processing stream request message: " + requestMsg);

        try {
            // Try to get a protocol implementation that matches the request message
            syncProtocol = getProtocolFactory().createReceivingSync(requestMsg);
        } catch (ProtocolCreationException ex) {
            log.warning("Processing stream request failed - " + Exceptions.unwrap(ex).toString());
            return new StreamResponseMessage(UpnpResponse.Status.NOT_IMPLEMENTED);
        }

        // Run it
        log.fine("Running protocol for synchronous message processing: " + syncProtocol);
        syncProtocol.run();

        // ... then grab the response
        StreamResponseMessage responseMsg = syncProtocol.getOutputMessage();

        if (responseMsg == null) {
            // That's ok, the caller is supposed to handle this properly (e.g. convert it to HTTP 404)
            log.finer("Protocol did not return any response message");
            return null;
        }
        log.finer("Protocol returned response: " + responseMsg);
        return responseMsg;
    }

其中,关键句syncProtocol.run(),调用到ReceivingAction#executeSync(),

通过getUpnpService().getConfiguration().getSoapActionProcessor().readBody(requestMessage, invocation)读取解析upnp消息,看RemoteActionInvocation和ActionInvocation定义,
public class RemoteActionInvocation extends ActionInvocation {

    final protected RemoteClientInfo remoteClientInfo;
    ......
}


public class ActionInvocation<S extends Service> {

    final protected Action<S> action;
    final protected ClientInfo clientInfo;
    ......
}

解析后,根据action信息进行指令派发回调处理,

    /**
     * Obtains the service implementation instance from the {@link ServiceManager}, handles exceptions.
     */
    public void execute(final ActionInvocation<LocalService> actionInvocation) {

        log.fine("Invoking on local service: " + actionInvocation);

        final LocalService service = actionInvocation.getAction().getService();

        try {

            if (service.getManager() == null) {
                throw new IllegalStateException("Service has no implementation factory, can't get service instance");
            }

            service.getManager().execute(new Command() {
                public void execute(ServiceManager serviceManager) throws Exception {
                    AbstractActionExecutor.this.execute(
                            actionInvocation,
                            serviceManager.getImplementation()
                    );
                }

                @Override
                public String toString() {
                    return "Action invocation: " + actionInvocation.getAction();
                }
            });

        } catch (ActionException ex) {
            if (log.isLoggable(Level.FINE)) {
                log.fine("ActionException thrown by service, wrapping in invocation and returning: " + ex);
                log.log(Level.FINE, "Exception root cause: ", Exceptions.unwrap(ex));
            }
            actionInvocation.setFailure(ex);
        } catch (InterruptedException ex) {
            if (log.isLoggable(Level.FINE)) {
                log.fine("InterruptedException thrown by service, wrapping in invocation and returning: " + ex);
                log.log(Level.FINE, "Exception root cause: ", Exceptions.unwrap(ex));
            }
            actionInvocation.setFailure(new ActionCancelledException(ex));
        } catch (Throwable t) {
            Throwable rootCause = Exceptions.unwrap(t);
            if (log.isLoggable(Level.FINE)) {
                log.fine("Execution has thrown, wrapping root cause in ActionException and returning: " + t);
                log.log(Level.FINE, "Exception root cause: ", rootCause);
            }
            actionInvocation.setFailure(
                new ActionException(
                    ErrorCode.ACTION_FAILED,
                    (rootCause.getMessage() != null ? rootCause.getMessage() : rootCause.toString()),
                    rootCause
                )
            );
        }
    }

最终,走到MethodActionExecutor#execute,

    protected void execute(ActionInvocation<LocalService> actionInvocation, Object serviceImpl) throws Exception {

        // Find the "real" parameters of the method we want to call, and create arguments
        Object[] inputArgumentValues = createInputArgumentValues(actionInvocation, method);

        // Simple case: no output arguments
        if (!actionInvocation.getAction().hasOutputArguments()) {
            log.fine("Calling local service method with no output arguments: " + method);
            Reflections.invoke(method, serviceImpl, inputArgumentValues);
            return;
        }

        boolean isVoid = method.getReturnType().equals(Void.TYPE);

        log.fine("Calling local service method with output arguments: " + method);
        Object result;
        boolean isArrayResultProcessed = true;
        if (isVoid) {

            log.fine("Action method is void, calling declared accessors(s) on service instance to retrieve ouput argument(s)");
            Reflections.invoke(method, serviceImpl, inputArgumentValues);
            result = readOutputArgumentValues(actionInvocation.getAction(), serviceImpl);

        } else if (isUseOutputArgumentAccessors(actionInvocation)) {

            log.fine("Action method is not void, calling declared accessor(s) on returned instance to retrieve ouput argument(s)");
            Object returnedInstance = Reflections.invoke(method, serviceImpl, inputArgumentValues);
            result = readOutputArgumentValues(actionInvocation.getAction(), returnedInstance);

        } else {

            log.fine("Action method is not void, using returned value as (single) output argument");
            result = Reflections.invoke(method, serviceImpl, inputArgumentValues);
            isArrayResultProcessed = false; // We never want to process e.g. byte[] as individual variable values
        }

        ActionArgument<LocalService>[] outputArgs = actionInvocation.getAction().getOutputArguments();

        if (isArrayResultProcessed && result instanceof Object[]) {
            Object[] results = (Object[]) result;
            log.fine("Accessors returned Object[], setting output argument values: " + results.length);
            for (int i = 0; i < outputArgs.length; i++) {
                setOutputArgumentValue(actionInvocation, outputArgs[i], results[i]);
            }
        } else if (outputArgs.length == 1) {
            setOutputArgumentValue(actionInvocation, outputArgs[0], result);
        } else {
            throw new ActionException(
                    ErrorCode.ACTION_FAILED,
                    "Method return does not match required number of output arguments: " + outputArgs.length
            );
        }

    }
Reflections.invoke(method, serviceImpl, inputArgumentValues),反射后调用render启动时注册的回调方法,完成设备控制指令消息的派发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

佳哥的技术分享

创作不易,谢谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值