目录
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)
这一句很关键。
它告诉我们
- ControlPoint 有一个 execute 方法
- 执行命令时 传入了一个 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启动时注册的回调方法,完成设备控制指令消息的派发。