在前两章《red5源码分析—3》中提到,在客户端与服务器握手期间,当客户端接收到red5服务器发送过来的S2信息后,会在connectionOpened函数中向服务器发送”connect”命令,为了方便分析,这里再贴一下客户端的这部分代码,
public void connectionOpened(RTMPConnection conn) {
Channel channel = conn.getChannel((byte) 3);
PendingCall pendingCall = new PendingCall("connect");
pendingCall.setArguments(connectArguments);
Invoke invoke = new Invoke(pendingCall);
invoke.setConnectionParams(connectionParams);
invoke.setTransactionId(1);
if (connectCallback != null) {
pendingCall.registerCallback(connectCallback);
}
conn.registerPendingCall(invoke.getTransactionId(), pendingCall);
channel.write(invoke);
}
下面就来看服务器是如何处理这个”connect”命令的。服务器接收到的消息首先经过RTMPEIoFilter服务器,根据《red5源码分析—4》的分析可知,这时候连接的状态已经变为STATE_CONNECTED,假设数据不加密,就直接到下一个过滤器,其他的过滤器这里就不分析了,最后会到达RTMPMinaIoHandler的messageReceived函数,
public void messageReceived(IoSession session, Object message) throws Exception {
String sessionId = (String) session.getAttribute(RTMPConnection.RTMP_SESSION_ID);
RTMPMinaConnection conn = (RTMPMinaConnection) RTMPConnManager.getInstance().getConnectionBySessionId(sessionId);
if (conn != null) {
if (message != null) {
if (message instanceof Packet) {
byte state = conn.getStateCode();
if (state != RTMP.STATE_DISCONNECTING && state != RTMP.STATE_DISCONNECTED) {
conn.handleMessageReceived((Packet) message);
} else {
}
}
}
} else {
forceClose(session);
}
}
RTMPMinaIoHandler的messageReceived函数根据sessionId获取到RTMPMinaConnection后,就调用其handleMessageReceived函数进行下一步处理。RTMPMinaConnection的handleMessageReceived函数定义在其父类RTMPConnection中,
public void handleMessageReceived(Packet message) {
final byte dataType = message.getHeader().getDataType();
switch (dataType) {
case Constants.TYPE_PING:
case Constants.TYPE_ABORT:
case Constants.TYPE_BYTES_READ:
case Constants.TYPE_CHUNK_SIZE:
case Constants.TYPE_CLIENT_BANDWIDTH:
case Constants.TYPE_SERVER_BANDWIDTH:
try {
handler.messageReceived(this, message);
} catch (Exception e) {
}
break;
default:
if (executor != null) {
final String messageType = getMessageType(message);
try {
final long packetNumber = packetSequence.incrementAndGet();
if (executorQueueSizeToDropAudioPackets > 0 && currentQueueSize.get() >= executorQueueSizeToDropAudioPackets) {
if (message.getHeader().getDataType() == Constants.TYPE_AUDIO_DATA) {
return;
}
}
if (maxHandlingTimeout > 0) {
message.setExpirationTime(System.currentTimeMillis() + maxHandlingTimeout);
}
int channelId = message.getHeader().getChannelId();
ReceivedMessageTask task = new ReceivedMessageTask(sessionId, message, handler, this);
task.setPacketNumber(packetNumber);
ReceivedMessageTaskQueue newChannelTasks = new ReceivedMessageTaskQueue(channelId, this);
ReceivedMessageTaskQueue currentChannelTasks = tasksByChannels.putIfAbsent(channelId, newChannelTasks);
if (currentChannelTasks != null) {
currentChannelTasks.addTask(task);
} else {
newChannelTasks.addTask(task);
}
} catch (Exception e) {
}
} else {
}
}
}
handleMessageReceived函数首先从消息message中获取dataType,根据《red5源码分析—3》可知,dataType默认为TYPE_INVOKE。executorQueueSizeToDropAudioPackets用于判断是否要丢弃音频数据包,maxHandlingTimeout用来设置每个数据包的最大处理时间,最后创建了一个ReceivedMessageTask并根据channelId通过addTask添加进ReceivedMessageTaskQueue中,
public void addTask(ReceivedMessageTask task) {
tasks.add(task);
Packet packet = task.getPacket();
if (packet.getExpirationTime() > 0L) {
task.runDeadlockFuture(new DeadlockGuard(task));
}
if (listener != null) {
listener.onTaskAdded(this);
}
}
ReceivedMessageTask实现了Java的Callable借口,因此最终会启用一个线程并执行其call函数,
public Packet call() throws Exception {
taskThread = Thread.currentThread();
Red5.setConnectionLocal(conn);
try {
handler.messageReceived(conn, packet);
packet.setProcessed(true);
} finally {
Red5.setConnectionLocal(null);
}
return packet;
}
call函数继续调用了handler的messageReceived函数,这里的handler是在sessionCreated中设置的RTMPHandler,其messageReceived定义在其父类BaseRTMPHandler中,
public void messageReceived(RTMPConnection conn, Packet packet) throws Exception {
if (conn != null) {
IRTMPEvent message = null;
try {
message = packet.getMessage();
final Header header = packet.getHeader();
final Number streamId = header.getStreamId();
final Channel channel = conn.getChannel(header.getChannelId());
final IClientStream stream = conn.getStreamById(streamId);
conn.setStreamId(streamId);
conn.messageReceived();
message.setSource(conn);
final byte headerDataType = header.getDataType();
switch (headerDataType) {
case TYPE_AGGREGATE:
case TYPE_AUDIO_DATA:
case TYPE_VIDEO_DATA:
message.setSourceType(Constants.SOURCE_TYPE_LIVE);
if (stream != null) {
((IEventDispatcher) stream).dispatchEvent(message);
}
break;
case TYPE_FLEX_SHARED_OBJECT:
case TYPE_SHARED_OBJECT:
onSharedObject(conn, channel, header, (SharedObjectMessage) message);
break;
case TYPE_INVOKE:
case TYPE_FLEX_MESSAGE:
onCommand(conn, channel, header, (Invoke) message);
IPendingServiceCall call = ((Invoke) message).getCall();
if (message.getHeader().getStreamId().intValue() != 0 && call.getServiceName() == null && StreamAction.PUBLISH.equals(call.getServiceMethodName())) {
if (stream != null) {
((IEventDispatcher) stream).dispatchEvent(message);
}
}
break;
case TYPE_NOTIFY:
case TYPE_FLEX_STREAM_SEND:
if (((Notify) message).getData() != null && stream != null) {
((IEventDispatcher) stream).dispatchEvent(message);
} else {
onCommand(conn, channel, header, (Notify) message);
}
break;
case TYPE_PING:
onPing(conn, channel, header, (Ping) message);
break;
case TYPE_BYTES_READ:
onStreamBytesRead(conn, channel, header, (BytesRead) message);
break;
case TYPE_CHUNK_SIZE:
onChunkSize(conn, channel, header, (ChunkSize) message);
break;
case Constants.TYPE_CLIENT_BANDWIDTH:
onClientBandwidth(conn, channel, (ClientBW) message);
break;
case Constants.TYPE_SERVER_BANDWIDTH:
onServerBandwidth(conn, channel, (ServerBW) message);
break;
default:
}
if (message instanceof Unknown) {
}
} catch (Throwable t) {
}
if (message != null) {
message.release();
}
}
}
RTMPMinaConnection的messageReceived函数用来做统计,因此这里不分析。下面获得dataType并根据该dataType进行相应的处理,”connect”命令对应的dataType为TYPE_INVOKE,因此调用onCommand进行处理,onCommand定义在RTMPHandler中。因为OnCommand较长,这里分为几个部分分析,
protected void onCommand(RTMPConnection conn, Channel channel, Header source, ICommand command) {
final IServiceCall call = command.getCall();
final String action = call.getServiceMethodName();
if ("_result".equals(action) || "_error".equals(action)) {
handlePendingCallResult(conn, (Invoke) command);
return;
}
boolean disconnectOnReturn = false;
boolean connected = conn.isConnected();
if (connected) {
...
} else {
if (StreamAction.CONNECT.equals(action)) {
final Map params = command.getConnectionParams();
String host = getHostname((String) params.get("tcUrl"));
String path = (String) params.get("app");
if (path.indexOf("?") != -1) {
int idx = path.indexOf("?");
params.put("queryString", path.substring(idx));
path = path.substring(0, idx);
}
params.put("path", path);
conn.setup(host, path, params);
try {
IGlobalScope global = server.lookupGlobal(host, path);
if (global != null) {
final IContext context = global.getContext();
IScope scope = null;
try {
scope = context.resolveScope(global, path);
if (scope.getDepth() < 1 && !globalScopeConnectionAllowed) {
call.setStatus(Call.STATUS_ACCESS_DENIED);
if (call instanceof IPendingServiceCall) {
IPendingServiceCall pc = (IPendingServiceCall) call;
StatusObject status = getStatus(NC_CONNECT_REJECTED);
status.setDescription("Global scope connection disallowed on this server.");
pc.setResult(status);
}
disconnectOnReturn = true;
}
if (scope != null) {
...
}
} catch (ScopeNotFoundException err) {
...
disconnectOnReturn = true;
} catch (ScopeShuttingDownException err) {
...
disconnectOnReturn = true;
}
} else {
...
disconnectOnReturn = true;
}
} catch (RuntimeException e) {
...
disconnectOnReturn = true;
}
if (new Double(3d).equals(params.get("objectEncoding"))) {
...
}
} else {
conn.close();
}
}
if (command instanceof Invoke) {
...
}
}
这里传入的action就是”connect”,并且这时候的RTMPMinaConnection连接并没有和任务red5的scope关联,因此connected为false。再往下调用setup函数将主机名host、应用路径path和参数params设置进connection中。接着调用lookupGlobal获取全局Scope,定义在Server中,
public IGlobalScope lookupGlobal(String hostName, String contextPath) {
String key = getKey(hostName, contextPath);
while (contextPath.indexOf(SLASH) != -1) {
key = getKey(hostName, contextPath);
String globalName = mapping.get(key);
if (globalName != null) {
return getGlobal(globalName);
}
final int slashIndex = contextPath.lastIndexOf(SLASH);
contextPath = contextPath.substring(0, slashIndex);
}
key = getKey(hostName, contextPath);
String globalName = mapping.get(key);
if (globalName != null) {
return getGlobal(globalName);
}
key = getKey(EMPTY, contextPath);
globalName = mapping.get(key);
if (globalName != null) {
return getGlobal(globalName);
}
key = getKey(hostName, EMPTY);
globalName = mapping.get(key);
if (globalName != null) {
return getGlobal(globalName);
}
key = getKey(EMPTY, EMPTY);
return getGlobal(mapping.get(key));
}
lookupGlobal函数简而言之,就是通过主机名和路径名获取key值,利用key值获取全局scope。
再继续看onCommand函数,获取全局scope后,就调用其context的resolveScope获取本次服务对应的scope。这里的context定义在red5-default.xml中,对应的类为org.red5.server.Context,下面来看它的resolveScope函数,
public IScope resolveScope(IScope root, String path) {
return scopeResolver.resolveScope(root, path);
}
Context的scopeResolver会被Spring自动注入为org.red5.server.scope.ScopeResolver,其resolveScope如下,
public IScope resolveScope(IScope root, String path) {
IScope scope = root;
if (StringUtils.isNotEmpty(path)) {
final String[] parts = path.split("/");
for (String child : parts) {
if (StringUtils.isEmpty(child)) {
continue;
}
if (!scope.hasChildScope(child) && !scope.equals(root)) {
scope.createChildScope(child);
}
scope = scope.getScope(child);
if (scope == null) {
throw new ScopeNotFoundException(scope, child);
}
if (scope instanceof IScope && scope.getType().equals(ScopeType.APPLICATION) && ((WebScope) scope).isShuttingDown()) {
throw new ScopeShuttingDownException(scope);
}
}
}
return scope;
}
resolveScope函数首先解析应用路径,对路径里的每个元素调用createChildScope创建子Scope,并返回该Scope,
public boolean createChildScope(String name) {
if (children.hasName(name)) {
} else {
return addChildScope(new Builder(this, ScopeType.ROOM, name, false).build());
}
return false;
}
public boolean addChildScope(IBasicScope scope) {
boolean added = false;
if (scope.isValid()) {
try {
if (!children.containsKey(scope)) {
added = children.add(scope);
} else {
}
} catch (Exception e) {
}
} else {
}
if (added && scope.getStore() == null) {
try {
if (scope instanceof Scope) {
((Scope) scope).setPersistenceClass(persistenceClass);
}
} catch (Exception error) {
}
}
return added;
}
这里主要就是调用children的add函数添加一个构造完的Scope,注意构造的Scope的默认类型为ScopeType.ROOM。children的类型为ConcurrentScopeSet,其add函数如下
public boolean add(IBasicScope scope) {
boolean added = false;
if (!containsKey(scope)) {
if (hasHandler()) {
IScopeHandler hdlr = getHandler();
if (!hdlr.addChildScope(scope)) {
return false;
}
} else {
}
try {
if (!containsKey(scope)) {
added = (super.put(scope, Boolean.TRUE) == null);
if (added) {
subscopeStats.increment();
} else {
}
} else {
}
} catch (Exception e) {
}
if (added && scope instanceof Scope) {
Scope scp = (Scope) scope;
if (scp.start()) {
} else {
}
}
}
return added;
}
add函数首先调用getHandler获取handler,
public IScopeHandler getHandler() {
if (handler != null) {
return handler;
} else if (hasParent()) {
return getParent().getHandler();
} else {
return null;
}
}
Scope刚被创建时,handler为null,因此这里会向其父Scope获取handler,所有Scope的最上层Scope都为GlobalScope,GlobalScope的handler为org.red5.server.CoreHandler(查看red5-default.xml),但是CoreHandler的addChildScope没有做任何动作,直接返回true。
再往下看,接下来就是调用其父类ConcurrentHashMap的put函数进行添加,再调用increment增加Scope的统计计数,最后调用Scope的start函数,代码如下
public boolean start() {
boolean result = false;
if (enabled && !running) {
if (handler != null) {
} else {
handler = parent.getHandler();
}
try {
if (handler != null) {
result = handler.start(this);
} else {
result = true;
}
} catch (Throwable e) {
} finally {
((Server) getServer()).notifyScopeCreated(this);
}
running = result;
}
return result;
}
这里获取父类的handler并赋值,从前面来看这里的handler就是CoreHandler,其start函数也是什么也没做,直接返回true。再往下就是调用Server类的notifyScopeCreated函数,通知有Scope创建了。这里就不往下看了。
回到addChildScope函数中,创建并成功添加新建的Scope后,就调用setPersistenceClass设置持久化类,根据red5-default.xml的配置,这里为org.red5.server.persistence.FilePersistence。
回到最前面的resolveScope函数中,创建完Scope后,就调用getScope获得该Scope并返回。
public IScope getScope(String name) {
IBasicScope child = children.getBasicScope(ScopeType.UNDEFINED, name);
if (child != null) {
if (child instanceof IScope) {
return (IScope) child;
}
}
return null;
}
public IBasicScope getBasicScope(ScopeType type, String name) {
boolean skipTypeCheck = ScopeType.UNDEFINED.equals(type);
if (skipTypeCheck) {
for (IBasicScope child : keySet()) {
if (name.equals(child.getName())) {
return child;
}
}
} else {
for (IBasicScope child : keySet()) {
if (child.getType().equals(type) && name.equals(child.getName())) {
return child;
}
}
}
return null;
}
这段代码很简单,这里就不分析了。
在获取了GlobalScope并在该GlobalScope下添加了相应的Scope后,就需要通过connect连接该Scope,继续往下看
protected void onCommand(RTMPConnection conn, Channel channel, Header source, ICommand command) {
...
if (connected) {
...
} else {
if (StreamAction.CONNECT.equals(action)) {
...
try {
IGlobalScope global = server.lookupGlobal(host, path);
if (global != null) {
final IContext context = global.getContext();
IScope scope = null;
try {
...
if (scope != null) {
boolean okayToConnect;
try {
if (call.getArguments() != null) {
okayToConnect = conn.connect(scope, call.getArguments());
} else {
okayToConnect = conn.connect(scope);
}
if (okayToConnect) {
call.setStatus(Call.STATUS_SUCCESS_RESULT);
if (call instanceof IPendingServiceCall) {
IPendingServiceCall pc = (IPendingServiceCall) call;
StatusObject result = getStatus(NC_CONNECT_SUCCESS);
result.setAdditional("fmsVer", Red5.getFMSVersion());
result.setAdditional("capabilities", Red5.getCapabilities());
result.setAdditional("mode", Integer.valueOf(1));
result.setAdditional("data", Red5.getDataVersion());
pc.setResult(result);
}
conn.ping(new Ping(Ping.STREAM_BEGIN, 0, -1));
disconnectOnReturn = false;
} else {
...
}
} catch (ClientRejectedException rejected) {
...
}
}
} catch (ScopeNotFoundException err) {
...
disconnectOnReturn = true;
} catch (ScopeShuttingDownException err) {
...
disconnectOnReturn = true;
}
} else {
...
disconnectOnReturn = true;
}
} catch (RuntimeException e) {
...
disconnectOnReturn = true;
}
if (new Double(3d).equals(params.get("objectEncoding"))) {
...
}
} else {
conn.close();
}
}
if (command instanceof Invoke) {
...
}
}
这里假设call.getArguments()返回null,对应的connect函数如下,定义在BaseConnection中,
public boolean connect(IScope newScope) {
return connect(newScope, null);
}
connect函数会依次调用其父类的connect函数,先来看最上层的connect如何处理的,
public boolean connect(IScope newScope, Object[] params) {
scope = (Scope) newScope;
return scope.connect(this, params);
}
因此这里就是简单调用新建的Scope进行连接,
public boolean connect(IConnection conn, Object[] params) {
if (enabled) {
if (hasParent() && !parent.connect(conn, params)) {
return false;
}
if (hasHandler() && !getHandler().connect(conn, this, params)) {
return false;
}
if (!conn.isConnected()) {
return false;
}
final IClient client = conn.getClient();
if (hasHandler() && !getHandler().join(client, this)) {
return false;
}
if (!conn.isConnected()) {
return false;
}
if (clients.add(client) && addEventListener(conn)) {
connectionStats.increment();
IScope connScope = conn.getScope();
if (this.equals(connScope)) {
final IServer server = getServer();
if (server instanceof Server) {
((Server) server).notifyConnected(conn);
}
}
return true;
}
} else {
}
return false;
}
首先调用该Scope的父Scope进行连接,其父类的连接函数依然为该函数,因此不往下看。接着调用handler的connect函数进行连接,从前面的分析可知,这里的handler就是CoreHandler,其connect函数如下
public boolean connect(IConnection conn, IScope scope, Object[] params) {
boolean connect = false;
String id = conn.getSessionId();
IScope connectionScope = conn.getScope();
if (connectionScope != null) {
IClientRegistry clientRegistry = connectionScope.getContext().getClientRegistry();
if (clientRegistry != null) {
IClient client = conn.getClient();
if (client == null) {
if (!clientRegistry.hasClient(id)) {
if (conn instanceof RTMPTConnection) {
client = new Client(id, (ClientRegistry) clientRegistry);
clientRegistry.addClient(client);
conn.setClient(client);
} else if (conn instanceof RTMPConnection) {
client = clientRegistry.newClient(params);
conn.setClient(client);
}
} else {
client = clientRegistry.lookupClient(id);
conn.setClient(client);
}
} else {
conn.setClient(client);
}
IConnectionManager connManager = RTMPConnManager.getInstance();
if (conn instanceof RTMPTConnection) {
connManager.setConnection((RTMPTConnection) conn);
} else if (conn instanceof RTMPConnection) {
connManager.setConnection((RTMPConnection) conn);
} else {
}
conn.initialize(client);
connect = true;
} else {
}
} else {
}
return connect;
}
这里connection中获得的Scope即前面创建好的Scope,其getContext函数会一直向上返回父类的Context,
public IContext getContext() {
if (!hasContext() && hasParent()) {
return parent.getContext();
} else {
return context;
}
}
因此最上层就是GlobalScope,其getContext返回的是org.red5.server.Context,其对应的getClientRegistry返回org.red5.server.ClientRegistry。
再往下,假设conn中取出的client为null,并且ClientRegistry中没有对应id的信息,这里就会构造一个客户端Client,将刚刚创建的Client添加进ClientRegistry和RTMPMinaConnection中(conn)。
再往下,RTMPConnManager的setConnection函数并没有实质性功能。最后调用了RTMPMinaConnection的initialize函数进行初始化,定义在RTMPMinaConnection的父类BaseConnection中,
public void initialize(IClient client) {
if (this.client != null && this.client instanceof Client && !this.client.equals(client)) {
((Client) this.client).unregister(this, false);
}
this.client = client;
if (this.client instanceof Client && !((Client) this.client).isRegistered(this)) {
((Client) this.client).register(this);
}
}
这里其实就是调用Client的register函数,
protected void register(IConnection conn) {
if (conn != null) {
IScope scope = conn.getScope();
if (scope != null) {
connections.add(conn);
} else {
}
} else {
}
}
这里就是将RTMPMinaConnection注册到Client中的一个集合connections中。
分析完CoreHandler中的connect函数后,再回头看Scope中的connect函数,再往下会继续调用CoreHandler的join函数,该函数简单返回true。再往下就是添加监听器,并同时服务器Server有新的连接了。
到此,分析完了RTMPMinaConnection父类的父类BaseConnection的connect函数,现在回过头看RTMPMinaConnection的父类RTMPConnection的connect函数
public boolean connect(IScope newScope, Object[] params) {
try {
boolean success = super.connect(newScope, params);
if (success) {
stopWaitForHandshake();
startRoundTripMeasurement();
} else {
}
return success;
} catch (ClientRejectedException e) {
...
}
}
当BaseConnection的connect函数成功返回后,这里首先调用stopWaitForHandshake取消握手,再调用startRoundTripMeasurement,
private void startRoundTripMeasurement() {
if (scheduler != null) {
if (pingInterval > 0) {
try {
keepAliveTask = scheduler.scheduleAtFixedRate(new KeepAliveTask(), pingInterval);
} catch (Exception e) {
}
}
} else {
}
}
这里其实就是启动了一个KeepAliveTask任务用来保持长连接,后面的章节会结合ping命令来分析这个任务。
最后再来看RTMPMinaConnection自己的connect函数,代码如下,
public boolean connect(IScope newScope, Object[] params) {
boolean success = super.connect(newScope, params);
if (success) {
final Channel two = getChannel(2);
two.write(new ServerBW(defaultServerBandwidth));
two.write(new ClientBW(defaultClientBandwidth, (byte) limitType));
if (client != null) {
if (bandwidthDetection && !client.isBandwidthChecked()) {
client.checkBandwidth();
}
} else {
}
registerJMX();
} else {
}
return success;
}
这里首先会调用Channel 2的write函数向客户端返回服务器和客户端的带宽信息,接着调用checkBandwidth检测客户端的带宽大小,最后调用registerJMX注册到JMX中。这里只看checkBandwidth函数,
public void checkBandwidth() {
bandwidthChecked = true;
ServerClientDetection detection = new ServerClientDetection();
detection.checkBandwidth(Red5.getConnectionLocal());
}
public void checkBandwidth(IConnection conn) {
calculateClientBw(conn);
}
public void calculateClientBw(IConnection conn) {
this.conn = conn;
Random rnd = new Random();
rnd.nextBytes(payload);
rnd.nextBytes(payload1);
startBytesWritten = conn.getWrittenBytes();
startTime = System.nanoTime();
callBWCheck("");
}
private void callBWCheck(Object payload) {
IConnection conn = Red5.getConnectionLocal();
Map statsValues = new HashMap();
statsValues.put("count", packetsReceived.get());
statsValues.put("sent", packetsSent.get());
statsValues.put("timePassed", timePassed);
statsValues.put("latency", latency);
statsValues.put("cumLatency", cumLatency);
statsValues.put("payload", payload);
if (conn instanceof IServiceCapableConnection) {
packetsSent.incrementAndGet();
((IServiceCapableConnection) conn).invoke("onBWCheck", new Object[] { statsValues }, this);
}
}
这里最后会出发客户端的onBWCheck函数进行测量。
分析完了所有的connect函数,回到OnCommand的第二部分,继续往下看如果连接成功就需要构造返回信息,包括服务器的版本号等等。并且调用RTMPMinaConnection的ping测试和客户端的速度。
第三部分就很简单了,这里就是将输出的结果call发送回客户端。
protected void onCommand(RTMPConnection conn, Channel channel, Header source, ICommand command) {
...
if (connected) {
...
} else {
...
}
if (command instanceof Invoke) {
if ((source.getStreamId().intValue() != 0) && (call.getStatus() == Call.STATUS_SUCCESS_VOID || call.getStatus() == Call.STATUS_SUCCESS_NULL)) {
return;
}
boolean sendResult = true;
if (call instanceof IPendingServiceCall) {
IPendingServiceCall psc = (IPendingServiceCall) call;
Object result = psc.getResult();
if (result instanceof DeferredResult) {
...
}
}
if (sendResult) {
Invoke reply = new Invoke();
reply.setCall(call);
reply.setTransactionId(command.getTransactionId());
channel.write(reply);
if (disconnectOnReturn) {
conn.close();
}
}
}
}