《Red5 用户参考手册》之四:入门第三章 迁移指南
本文档介绍了 Macromedia Flash 通信服务器/Adobe Flash 媒体服务器和 Red5 之间的 API 差异,目的是帮助我们将现有的应用转移至 Red5。
如果你还没有基于 Red5 的应用,请先阅读有关创建一个新的 Red5 应用程序的文档。
程序回调
当实现一个服务端的程序时,最重要的功能之一就是通知客户是否连接成功并等待应用程序的新实例的创建的通知。
IScopeHandler 接口
Red5 将这些行为指定在 IScopeHandler 接口 http://dl.fancycode.com/red5/api/org/red5/server/api/IScopeHandler.html。请阅读 API 文档来获得详细信息。
ApplicationAdapter 类
鉴于有些方法可能会在一个请求中多次被调用到(比如,连接可能会被客户端连接到的树中的每一个域都调用一次),类 ApplicationAdapter http://dl.fancycode.com/red5/api/org/red5/server/adapter/ApplicationAdapter.html 定义了附加方法。
这个类经常被用来作为新应用的基类。
以下是 FCS/FMS 的 application 类和 Red5 的 ApplicationAdapter http://dl.fancycode.com/red5/api/org/red5/server/adapter/ApplicationAdapter.html 类的方法的一个简单对比。
FCS / FMS | Red5 |
onAppStart | appStart \\ roomStart |
onAppStop | appStop \\ roomStop |
onConnect | appConnect \\ roomConnect \\ appJoin \\ roomJoin |
onDisconnect | appDisconnect \\ roomDisconnect \\ appLeave \\ roomLeave |
你也可以使用类 ApplicationAdapter http://dl.fancycode.com/red5/api/org/red5/server/adapter/ApplicationAdapter.html 来检查流、共享对象。
连接方法的执行顺序
假设你连接到 rtmp://server/app/room1/room2
首先,连接建立,用户"连接"所有域跨越至 room2:
1.app (-> appConnect)
2.room1 (-> roomConnect)
3.room2 (-> roomConnect)
连接建立以后,客户端对象被检索,如果是第一次连接,将"连接"域:
1.app (-> appJoin)
2.room1 (-> roomJoin)
3.room2 (-> roomJoin)
如果同一个客户端再次连接到同一个域,就只会调用 connect 方法。如果你恰好连接到同一些域,可能只会调用到一小部分的 join 方法,例如,rtmp://server/app/room1/room3 会触发
1.appConnect("app")
2.joinConnect("room1")
3.joinConnect("room3")
4.roomJoin("room3")
此时 appStart 仅在 Red5 启动时调用一次,它并不能像 FCS/FMS 那样卸载/加载程序。当第一个客户端连接到域时 roomStart 被调用。
接受/拒绝客户端
FCS/FMS 提供 acceptConnection 和 rejectConnection 方法来接受或者拒绝新的客户端。如若允许客户端连接,Red5 应用只需让 *Connect 方法返回 true 即可。
如果不允许某个客户端连接,可以调用由类 ApplicationAdapter http://dl.fancycode.com/red5/api/org/red5/server/adapter/ApplicationAdapter.html 实现的 rejectClient 方法。任何传递到 rejectClient 的参数作为返回给调用者的有状态对象的程序属性都是可用的。
当前连接和客户端
Red5 支持两种不同的方式来从一个被调用的方法中访问当前连接。连接可以用于获取活动客户端以及他连接到的域。第一种可能性(方式)就是使用"神奇的"Red5http://dl.fancycode.com/red5/api/org/red5/server/api/Red5.html 对象:
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
public void whoami() {
IConnection conn = Red5.getConnectionLocal();
IClient client = conn.getClient();
IScope scope = conn.getScope();
// ...
}
第二种可能性(方式)需要这个方法定义一个 IConnection http://dl.fancycode.com/red5/api/org/red5/server/api/IConnection.html 类型的参数作为隐式的第一个参数,当一个客户端调用此方法时这个参数会被 Red5 自动添加:
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
public void whoami(IConnection conn) {
IClient client = conn.getClient();
IScope scope = conn.getScope();
// ...
}
附加的处理程序
对于多数(迁移)程序来说,包含的业务逻辑与 Red5 无关的类可以被复用。为了让他们可以被通过 RTMP 连接的客户端所用,这些类需要在 Red5 中注册为处理程序。
眼下有两个办法来注册这些处理程序:
1.把它们添加到配置文件中去
2.在应用程序中手工注册
这些处理程序可以被客户端直接调用:
nc = new NetConnection();
nc.connect("rtmp://localhost/myapp");
nc.call("handler.method", nc, "Hello world!");
当一个处理程序被请求到时,Red5 会在检查通过配置文件创建的处理程序之前先在自定义域的处理程序中检查。
通过配置文件注册处理程序
这种方法是最适合于对所有的应用程序运行和范围都可见的,而且并不需要在应用程序的生命周期期间进行改变的处理函数。
举例,将类 com.fancycode.red5.HandlerSample 作为一个处理程序进行注册,以下 bean 需要添加到 WEB-INF/red5-web.xml:
<bean id="sample.service"
class="com.fancycode.red5.HandlerSample"
singleton="true" />
请注意 bean 的 id 是由处理程序的名字(在这里是 sample)和 service 关键字组成的。
通过应用程序中手工注册处理程序
对于因对于域的不同而需要不同的处理程序或者想要改动处理程序的(Red5)应用程序来说,需要将处理程序在服务端代码中进行注册。这些处理程序常常需要重写配置在 red5-web.xml 的处理程序。这些需要注册的方法在 IServiceHandlerProvider http://dl.fancycode.com/red5/api/org/red5/server/api/service/IServiceHandlerProvider.html 进行描述,由 ApplicationAdapter http://dl.fancycode.com/red5/api/org/red5/server/adapter/ApplicationAdapter.html 类实现。
仍然拿上边 com.fancycode.red5.HandlerSample 类作为例子,在代码中手工注册如下所示:
public boolean appStart(IScope app) {
if (!super.appStart(scope))
return false;
Object handler = new com.fancycode.red5.HandlerSample();
app.registerServiceHandler("sample", handler);
return true;
}
请注意在本例子中,只有当前程序域持有处理程序,而其子域没有!如果想要这个处理程序也对 room 有效,那它应该在 roomStart 方法中为 room 域进行注册。
调用客户端方法
Red5 应用程序要想调用客户端方法的话,首先得需要一个对当前连接对象的引用:
import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.api.service.IServiceCapableConnection;
...
IConnection conn = Red5.getConnectionLocal();
如果连接实现了 IServiceCapableConnection http://dl.fancycode.com/red5/api/org/red5/server/api/service/IServiceCapableConnection.html 接口的话,它将支持调用另一端的方法:
if (conn instanceof IServiceCapableConnection) {
IServiceCapableConnection sc = (IServiceCapableConnection) conn;
sc.invoke("the_method", new Object[]{"One", 1});
}
如果你需要拿到方法执行的结果,那你需要提供一个实现了 IPendingServiceCallback http://dl.fancycode.com/red5/api/org/red5/server/api/service/IPendingServiceCallback.html 接口的类:
import org.red5.server.api.service.IPendingService;
import org.red5.server.api.service.IPendingServiceCallback;
class MyCallback implements IPendingServiceCallback {
public void resultReceived(IPendingServiceCall call) {
// Do something with "call.getResult()"
}
}
调用方法的代码如下:
if (conn instanceof IServiceCapableConnection) {
IServiceCapableConnection sc = (IServiceCapableConnection) conn;
sc.invoke("the_method", new Object[]{"One", 1}, new MyCallback());
}
当然,你也可以在程序中实现这个接口,并将其实例进行传递。
对象共享
关于访问应用中共享的对象的方法是在接口 ISharedObjectService http://dl.fancycode.com/red5/api/org/red5/server/api/so/ISharedObjectService.html 中定义的。
当在服务端脚本中处理共享的对象时,应特别注意它们的创建所在的域。
当一个 room 创建时要创建一个新的共享对象的话,你可以在应用中重写 roomStart 方法:
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IScope;
import org.red5.server.api.so.ISharedObject;
public class SampleApplication extends ApplicationAdapter {
public boolean roomStart(IScope room) {
if (!super.roomStart(room))
return false;
createSharedObject(room, "sampleSO", true);
ISharedObject so = getSharedObject(room, "sampleSO");
// Now you could do something with the shared object...
return true;
}
}
于是每次用户首次连接到应用的 room 时,比如通过 rtmp://server/application/room1 ,一个共享对象 sampleSO 将由服务创建。
而如果一个共享对象应该由主应用创建的话,比如 rtmp://server/application,就应该在 appStart 进行以上代码了。
请参考 ISharedObject http://dl.fancycode.com/red5/api/org/red5/server/api/so/ISharedObject.html 来获得更多关于对象共享的相关方法。
服务端变化监听器
如果想像在 FCS / FM 中的 onSync 方法那样得到在共享对象变化时的通知,监听器就应该实现 ISharedObjectListenerhttp://dl.fancycode.com/red5/api/org/red5/server/api/so/ISharedObjectListener.html 接口:
import org.red5.server.api.so.ISharedObject;
import org.red5.server.api.so.ISharedObjectListener;
public class SampleSharedObjectListener
Migration Guide
implements ISharedObjectListener {
public void onSharedObjectUpdate(ISharedObject so,
String key, Object value) {
// The attribute <key> of the shared object <so>
// was changed to <value>.
}
public void onSharedObjectDelete(ISharedObject so, String key) {
// The attribute <key> of the shared object <so> was deleted.
}
public void onSharedObjectSend(ISharedObject so,
String method, List params) {
// The handler <method> of the shared object <so> was called
// with the parameters <params>.
}
// Other methods as described in the interface...
}
另外,这个监听器还必须得在共享对象中注册一下:
ISharedObject so = getSharedObject(scope, "sampleSO");
so.addSharedObjectListener(new SampleSharedObjectListener())
应用程序的更改
一个共享对象也可以由服务这样进行更改:
ISharedObject so = getSharedObject(scope, "sampleSO");
so.setAttribute("fullname", "Sample user");
在这里所有相关客户端和注册进来的处理程序都将会被通知属性的添加/更改。
如果想把共享对象上的多个动作并入一个对于相关客户端改变事件的话,必须得用 beginUpdate 和 endUpdate 方法:
ISharedObject so = getSharedObject(scope, "sampleSO");
so.beginUpdate();
so.setAttribute("One", "1");
so.setAttribute("Two", "2");
so.removeAttribute("Three");
so.endUpdate();
但服务端的监听器不受此影响。
Flash 客户端通过 remote_so.send(<handler>, <args>) 对共享对象的处理程序的调用或者相应服务端调用可以在 Red5 映射到方法。因此处理程序必须通过 ISharedObjectHandlerProviderhttp://dl.fancycode.com/red5/api/org/red5/server/api/so/ISharedObjectHandlerProvider.html 接口的方法注册:
package com.fancycode.red5;
class MySharedObjectHandler {
public void myMethod(String arg1) {
// Now do something
}
}
...
ISharedObject so = getSharedObject(scope, "sampleSO");
so.registerServiceHandler(new MySharedObjectHandler());
处理程序也可以在注册时命名:
ISharedObject so = getSharedObject(scope, "sampleSO");
so.registerServiceHandler("one.two", new MySharedObjectHandler());
这里,方法可以通过 one.two.myMethod 被调用。另一个定义共享对象的事件处理程序就是将其添加进 red5-web.xml,类似于基于文件的应用处理程序。bean 必须带有 <SharedObjectName>.<DottedServiceName>.soservice 名字,因此以上例子也可以这样定义:
<bean id="sampleSO.one.two.soservice"
class="com.fancycode.red5.MySharedObjectHandler"
singleton="true" />
持久化
持久化是为了服务重启以后仍然可以用到重启前对象的属性。在 FCS / FMS 中服务端的本地共享对象常常如此利用。
Red5 允许随意对象的持久化操作,所有他们需要去做的就是实现接口 IPersistable http://dl.fancycode.com/red5/api/org/red5/server/api/persistence/IPersistable.html。基本上来说这些对象有一个类型,一个路径,一个名字,并且知道如何进行自身的序列化、反序列化。
以下是一个序列化和反序列化的例子:
import java.io.IOException;
import org.red5.io.object.Input;
import org.red5.io.object.Output;
import org.red5.server.api.persistence.IPersistable;
class MyPersistentObject implements IPersistable {
// Attribute that will be made persistent
private String data = "My persistent value";
void serialize(Output output) throws IOException {
// Save the objects's data.
output.writeString(data);
}
void deserialize(Input input) throws IOException {
// Load the object's data.
data = input.readString();
}
// Other methods as described in the interface...
}
要保存或者加载这样一个对象,可以使用如下代码:
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.persistence.IPersistenceStore;
class MyApplication extends ApplicationAdapter {
private void saveObject(MyPersistentObject object) {
// Get current scope.
IScope scope = Red5.getConnectionLocal().getScope();
// Save object in current scope.
scope.getStore().save(object);
}
private void loadObject(MyPersistentObject object) {
// Get current scope.
IScope scope = Red5.getConnectionLocal().getScope();
// Load object from current scope.
scope.getStore().load(object);
}
}
如果程序没有自定义对象,但数据必须保存下来以备以后使用,它可以通过接口 IAttributeStore http://dl.fancycode.com/red5/api/org/red5/server/api/IAttributeStore.html 加入到 IScope http://dl.fancycode.com/red5/api/org/red5/server/api/IScope.html。在域中,所有属性并不随 IPersistable 开始。TRANSIENT_PREFIX 是持久性的。
用来存放对象的后台是可以进行配置的。默认情况下,内存和文件系统都是可以进行用来持久化对象的。
当使用文件系统进行对象持久化时会在 "webapps/<app>/ persistence/<type>/<path>/<name>.red5" 里创建一个文件,例如,当连接到 "rtmp://server/myApp/room1" 的一个共享对象 "theSO" 持久化时,文件 "webapps/myApp/persistence/ SharedObject/room1/theSO.red5" 会被创建。
周期性事件
FCS / FMS 的应用常使用 setInterval 来周期性地执行任务计划方法。
Red5 提供了一个计划服务(ISchedulingService http://dl.fancycode.com/red5/api/org/red5/server/api/scheduling/ISchedulingService.html),也由 ApplicationAdapter http://dl.fancycode.com/red5/api/org/red5/server/adapter/ApplicationAdapter.html 像实现其他接口似的实现了。这个服务可以注册进来一个对象(这个对象需要实现接口 IScheduledJob http://dl.fancycode.com/red5/api/org/red5/server/api/scheduling/IScheduledJob.html),这个对象的执行方法可以在指定间隔内进行调用。
可以像这样进行对象的注册:
import org.red5.server.api.IScope;
import org.red5.server.api.IScheduledJob;
import org.red5.server.api.ISchedulingService;
import org.red5.server.adapter.ApplicationAdapter;
class MyJob implements IScheduledJob {
public void execute(ISchedulingService service) {
// Do something
}
}
public class SampleApplication extends ApplicationAdapter {
public boolean roomStart(IScope room) {
if (!super.roomStart(room))
return false;
// Schedule invokation of job every 10 seconds.
String id = addScheduledJob(10000, new MyJob());
room.setAttribute("MyJobId", id);
return true;
}
}
这个由 addScheduledJob 返回的 id 以后可以用来停止注册进来的工作的执行:
public void roomStop(IScope room) {
String id = (String) room.getAttribute("MyJobId");
removeScheduledJob(id);
super.roomStop(room);
}
远程
远程可以被非 rtmp 客户端用来调用 Red5 的方法。另一种可能性是其他服务器,提供了一个远程服务调用 Red5 的方法。
远程服务器
服务如果对客户端可用的话,需要向附加应用处理程序一样的方法进行注册。详情请看上文。
为了支持远程调用,在 WEB-INF/web.xml 文件里需要添加以下部分:
<servlet>
<servlet-name>gateway</servlet-name>
<servlet-class>
org.red5.server.net.servlet.AMFGatewayServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>gateway</servlet-name>
<url-pattern>/gateway/*</url-pattern>
</servlet-mapping>
在 <url-pattern> 标签中指定的路径(这里是 gateway)就可以作为连接 url 被远程客户端调用。假如应用 myApp 已经制定了本示例,那么这个 URL 可以是:
http://localhost:5080/myApp/gateway
通过被连接被调用的方法将会在当前应用域的上下文中执行。如果想要这些方法在子域执行的话,URL 中就应该加上相应的子域:
http://localhost:5080/myApp/gateway/room1/room2
远程客户端
RemotingClient http://dl.fancycode.com/red5/api/org/red5/server/net/remoting/RemotingClient.html 类定义了需要通过远程协议执行的方法。
以下服务端代码示例解释了如何使用远程客户端:
import org.red5.server.net.remoting.RemotingClient;
String url = "http://server/path/to/service";
RemotingClient client = new RemotingClient(url);
Object[] args = new Object[]{"Hello world!"};
Object result = client.invokeMethod("service.remotingMethod", args);
// Now do something with the result
默认情况下,每次调用会有一个 30 秒的过期时间,这个时间可以通过定义了过期时间参数的构造函数进行修改,这个参数的单位是毫秒。
远程头AppendToGatewayUrl, ReplaceGatewayUrl 和 RequestPersistentHeader 由 Red5 远程客户端自动处理。
被调用服务器上的一些方法可能需要相当长的时间才能执行完,因此为了避免线程阻塞 Red5 服务最好是异步执行调用。因此应该有一个实现了接口 IRemotingCallbackhttp://dl.fancycode.com/red5/api/org/red5/server/net/remoting/IRemotingCallback.html 的对象作为附加参数:
import org.red5.server.net.remoting.RemotingClient;
import org.red5.server.net.remoting.IRemotingCallback;
public class CallbackHandler implements IRemotingCallback {
void errorReceived(RemotingClient client, String method,
Object[] params, Throwable error) {
// An error occurred while performing the remoting call.
}
void resultReceived(RemotingClient client, String method,
Object[] params, Object result) {
// The result was received from the server.
}
}
String url = "http://server/path/to/service";
RemotingClient client = new RemotingClient(url);
Object[] args = new Object[]{"Hello world!"};
IRemotingCallback callback = new CallbackHandler();
client.invokeMethod("service.remotingMethod", args, callback);
流
TODO:如何在应用中访问流?
原文链接: http://trac.red5.org/wiki/Documentation/UsersReferenceManual/GettingStarted/03-Migration-Guide。