从FCS/FMS迁移到red5的指南

来源:互联网 发布:中国西方古典音乐 知乎 编辑:程序博客网 时间:2024/06/11 00:15

migration guide form FCS/FMS  to red 5 :: FCS/FMS迁移到red5的指南

 

Author: Joachim Bauch  tranlater:yapollo.li  email:yapollo.li@gmail.com

作者:JoachimBauch   翻译:yapollo.li 

 

Contents 内容介绍:

  • Preface 序文
  • Application callbacks   应用程序回调
    • Interface IScopeHandler IScopeHandler(作用域事件)接口
    • Class ApplicationAdapter ApplicationAdapter
      • Execution order of connection methods 连接方法的执行命令
    • Accepting / rejecting clients 接收和拒绝客户端
  • Current connection and client 当前的连接和客户端
  • Additional handlers  附加事件处理
    • Handlers in configuration files 在配置文件中注册处理事件
    • Handlers from application code 在应用程序代码中注册处理事件
  • Calls to client methods  调用客户端的方法
  • SharedObjects  共享对象
    • Serverside change listeners 服务器端执行更改共享对象的事件监听处理
    • Changing from application code 从应用程序去更改共享对象
    • SharedObject event handlers 共享对象的事件处理
  • Persistence 持久性(持续性)
  • Periodic events 循环事件
  • Remoting 远程调用
    • Remoting server 远程调用服务器端
    • Remoting client 远程调用客户端
  • Streams 流媒体

 

 

 

 

 

 

 

 

序文:

这篇文档描述了fcs/fmsred5之间的API(应用程序接口)的不同,目的在于帮助那些已经存在于FCS/FMS的应用程序迁移到RED5上,如果你还没有点red5经验,请阅读这方面的指南howtocreate new applications

应用程序回调

在实现服务器端应用程序的时候一个重要的功能就是通告客户端是否链接,以及当应用程序有新的实例创建的时候能够通知该消息。

InterfaceIScopeHandler

Red5在接口IScopeHandler指定了这些动作 大家可以看它的api文档去进一步了解细节

 

ClassApplicationAdapter

ApplicationAdapter 类介绍:

因于一次请求中一些方法可能被重复的调用(比如:一旦有新客户端链接产生的时,connect方法就会被调用),所以这个类ApplicationAdapter 中定义了许多附加的方法事件

这个类通常被新的应用程序用作基础类

下面是一个一览表,它显示了FCS/FMS应用程序类和red5ApplicationAdapter方法的对比

FCS / FMS                  Red5

 

onAppStart                  appStart roomStart

onAppStop                             appStop roomStop

appConnect                            roomConnect appJoin roomJoin

onDisconnect                                appDisconnect roomDisconnect appLeaveroomLeave

 

你也可以使用ApplicationAdapter去核对流媒体对象,共享对象,或者预定义他们。关于进一步的细节可以参考它的api文档

app做前缀的方法,是在主应用程序中被调用滴,以room做前缀的方法是在应用程序的房间(也就是实例中)被调用

 

 

链接方法的执行命令

假设你要链接rtmp://server/app/room1/room2

首先 ,建立连接,那么所有链接到room2的用户都能被全部显示,

  1. app (-> appConnect)
  2. room1 (-> roomConnect)
  3. room2 (-> roomConnect)

 

在连接被建立后,客户端对象能被取回,并且如果它(指取回的客户端对象)是第一个被客户端连接到这个作用域的,他就加入该作用域

  1. app (-> appJoin)
  2. room1 (-> roomJoin)      
  3. room2 (-> roomJoin)

 

如果在同一作用域内一个用户建立了第二个(或者多余)链接,那么只有这些连接方法被调用,如果你连接到部分相同作用域内,那么只有部分加入方法被调用

比如:连接 rtmp://server/app/room1/room3 将会触发下面的语句

  1. appConnect("app")
  2. joinConnect("room1")
  3. joinConnect("room3")
  4. roomJoin("room3")

appStart方法在开启red5服务的时候,当前只能被调用一次,

roomStart方法在第一个客户端连接到房间的时候被调用

 

接受,拒绝客户端

FCS/FMS 提供了如acceptConnection rejectConnection的方法去接受和拒绝新的客户端。对Red5应用程序,允许客户端连接,没有专门的动作是必须得在这种情况下后缀为Connect的方法只是需要返回true

如果客户端不允许去连接,在ApplicationAdapter类中的rejectClient方法将会被调用,任何被传递给rejectClient的参数都是有效滴,会被看做状态对象的应用程序的属性从而被回调函数返回

 

当前的连接和客户端访问

Red5支持两种不同的方法,从被调用函数中访问当前的连接对象,这个连接对象能被用于获取活跃的客户端和他被连接的作用域。第一个使用 Red5 对象

代码如下:

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类型的参数,同时当一个客户端调用该方法时第一个参数会被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();
    // ...
}

Additionalhandlers

附加事件的处理

对于许多应用程序而言,已经存在的类包含的应用程序逻辑(指那些和red5不相关的)是必须被重复使用的,为了能对那些通过RTMP连接的客户端有效,这些类需要在Red5中作为事件注册。

当前有两个方法去注册这些事件:

1.   通过配置文件去增加他们

2.   通过手工从应用程序中注册他们

这些事件被客户端调用执行的示例代码如下:

nc = new NetConnection();
nc.connect("rtmp://localhost/myapp");
nc.call("handler.method", nc, "Hello world!");

如果一个事件处理器被请求,Red5总是会在检测配置文件环境中的设置的事件处理器之前先去在客户端的作用域范围内寻找它。

Handlersin configuration files

在配置文件中处理事件

这种方法更适用于那些对所有作用域的程序都运行的常见事件

下面是一个事件处理的例子,它的作用是去注册类com.fancycode.red5.HandlerSample

下面的bean 需要加入WEB-INF/red5-web.xml的文件中。

<bean id="sample.service" 
      class="com.fancycode.red5.HandlerSample" 
      singleton="true" />

Note that the id of the bean is constructed as the name ofthe handler (here sample)and the keyword service.

注意这个beanid 被构建作为处理器程序和关键字服务的名字

 

 

Handlersfrom application code

在应用程序中用代码注册事件处理

不同作用域的应用程序使用不同的事件处理,如果想去更改它们,就需要在服务端的代码中注册他们。写在服务端的这些事件处理总会覆盖在red5-web.xml中配置的事件。这些已经注册过的必要函数被命名在IServiceHandlerProvider中,当然IServiceHandlerProviderApplicationAdapter类中实现哈

如同上面描述的类文件能被用代码注册的例子如下:

public boolean appStart(IScope app) {
    if (!super.appStart(scope))
        return false;
    
    Object handler = new com.fancycode.red5.HandlerSample();
    app.registerServiceHandler("sample", handler);
    return true;
}
 
注意:在这个例子中,只是这个应用程序的作用域有sample处理程序,而不是子作用域,如果想这个处理程序同样在房间中起作用,必须在房间的作用域内的roomStart方法中注册它。
 

Calls to clientmethods

调用客户端的方法

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,那么它就支持调用在另一个目标的方法

代码如下:

if (conn instanceof IServiceCapableConnection) {
    IServiceCapableConnection sc = (IServiceCapableConnection) conn;
    sc.invoke("the_method", new Object[]{"One", 1});
}

如果你需要方法调用的结果,你必须提供一个类作为第三个参数,这个类执行IPendingServiceCallback

代码如下:

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()"
    }
}

The method call looks now like this:

这个方法的调用例子如下:

if (conn instanceof IServiceCapableConnection) {
    IServiceCapableConnection sc = (IServiceCapableConnection) conn;
    sc.invoke("the_method", new Object[]{"One", 1}, new MyCallback());
}

当然你能够在你的应用程序中执行这些,而且能传递该应用程序的实例

 

SharedObjects

共享对象

访问和处理共享对象的接口函数已经被指定在ISharedObjectService类中

当我们在服务器端执行脚本处理共享对象的时候,需要特别指出的是这些被操作的共享对象一定要是在同一个作用域中被创建的。

当一个房间被被创建的同时创建一个新的共享对象,你可以在应用程序中重写你的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;            
  }
  
}

 

现在每次第一个用户连接到该应用程序的房间时,一个共享对象 sampleSO就会被服务器端创建.

一个共享对象应该被创建作为主程序的连接点,例如:rtmp://server/application  那么这个创建动作就应该在函数appStart中进行.

关于进一步的共享对象的函数信息,请大家参考ISharedObjectapi参考文档

Serversidechange listeners

服务器端执行改变监听事件

要像在FCS/FMS一样,得到共享对象的改变信息,一个监听器,就必须实现ISharedObjectListener类,示例代码如下:

import org.red5.server.api.so.ISharedObject;
import org.red5.server.api.so.ISharedObjectListener;
 
public class SampleSharedObjectListener
       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())

 

Changingfrom application code

从应用程序代码去改变共享对象的值

在服务器端改变共享对象示例代码如下:

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();
 
 

服务器端执行的监听器将会通过各自的回调函数收到他们的更新通知

SharedObjectevent handlers

共享对象事件处理

从一个flash客户端或者相应的服务器端,通过remote_so.send(<handler>,<args>)调用共享对象,这个调用会被映射为red5的函数,因此一个事件处理程序必须通过ISharedObjectHandlerProvider的函数被注册。示例代码如下:

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 id="sampleSO.one.two.soservice" 
      class="com.fancycode.red5.MySharedObjectHandler" 
      singleton="true" />

Persistence

持续性(持久性)

持续性被用于如在服务器重启后,对象的属性仍然有效能被使用。在FCS/FMS中通常有些服务器端的局部共享对象被用于这方面

Red5能使任意的对象具有持续性(持久性),实现他们需要使用IPersistable类,基本上这些对象都会有 类型,路径,字符串名称 并且他们知道怎样去序列化和反序列化他们本身

下面是个序列化和反序列化的例子:

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,将他们存储在作用域内,作用域内的所有属性都不是用IPersistable方式开启的,

 

后端存储这些对象的方式是可以配置的,默认的状态下存储在内存和文件系统中是有效滴

使用文件系统的存储方式时,会为每一个对象创建一个文件,这个文件会被创建在 "webapps/<app>/persistence/<type>/<path>/<name>.red5",例如:对于一个共享对象“theSO”存在于"rtmp://server/myApp/room1"的连接中,同时一个文件会创建在"webapps/myApp/persistence/SharedObject/room1/theSO.red5"

 

Periodic events

循环事件

应用程序中都需要按规律执行某个任务,在FCS/FMS中使用setInterval函数去执行循环事件

Red5提供了一个时序安排服务在ISchedulingService类中,该类也和其他服务一样是继承自ApplicationAdapter类。这个服务能够注册一个对象(这个对象需要实现IScheduledJob类)执行以固定时间间隔去执行函数

去注册这个对象的类似示例代码如下:

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);
}
 

Remoting

远程调用

远程调用能够被非rtmp协议的客户端使用去调用在red5的函数。另一种可能就是从red5去其它提供远程调用服务的服务器调用他们的函数。

Remoting server

远程调用服务

被远程调用的服务要对客户端有效的话,需要和应用程序事件一样被注册。

为了使远程调用被应用支持,下面一段代码必须被添加在文件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
 
 
Remoting client

远程调用客户端

RemotingClient中定义了所有需要通过远程调用协议调用的函数

下面的代码可以做为怎样使用远程调用客户端的例子:

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秒,不过这个值能通过给构造函数传递第二个参数被改变, 而且这个最大超时间的设置单位是毫秒

The remoting headers AppendToGatewayUrl, ReplaceGatewayUrland RequestPersistentHeaderare handled automatically by the Red5 remoting client.

这个远程调用的消息头AppendToGatewayUrl, ReplaceGatewayUrland RequestPersistentHeader能自动被red5的远程调用客户端处理

一些在服务端被调用的函数可能耗费比较长的时间才能完成,因此最好是用异步的方式执行调用,避免在Red5线程中死锁。所以一个在IRemotingCallback中实现的对象必须被做为附件的参数传递,示例代码如下:
 
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);
 
 

 

 
原创粉丝点击