Red5学习笔记(二)连接通信

本文详细解析了Red5服务器中的全局Scope、WebScope、ApplicationAdapter、Room对象及其内部结构,以及如何实现客户端之间的通信流程。包括创建连接、使用SharedObject传递信息、维护用户列表等关键步骤。
Red服务器对象关系:
    首先服务器会有一个全局的Scope对象 ,它包含每一个应用对应的WebScope对象,一个WebScope对象包含唯一的ApplicationAdapter对象;一个ApplicationAdapter对象可以继续创建从属于自己的多个Room对象。WebScope对象和Room对象都是IScope对象的具体实现,它们都包含一个IClient类型的集合体Set。IClient对象包含一个IConnection类型的集合体Set。用于通信的SharedObject生存在WebScope或者Room里。

通信流程:
   1、 当ActionScript 3里的NetConnection对象尝试连接Red5的某个应用时,Red5服务器端对应的WebScope会响应这个请求,将接收到的连接转换成一个IClient对象,保存在当前的Scope,也就是Web Scope的Set<IClient>里,这样就完成了创建连接的过程。
    2、在RTMP协议下,有两种方法可以在客户端之间传递信息:
      a、使用服务器端SharedObject传递信息。该方法会尝试支所有连接成功的客户端广播信息,效率更好。
      b、使用服务器端NectConnection 调用 客户端Client自定义方法。该方法可对指定的客户传播信息,更加准确。
    3、当客户端连接成功时,Red5服务器端创建出一个SharedObject,用于不同客户端之间共享信息。
服务器端代码:
package com.red5app;
import java.util.HashMap;
import java.util.Map;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.so.ISharedObject;
/**
* 在Red5的服务器端类体系里,每一个监听的应用实例都需要继承类ApplicationAdapter
* 项目需要引用Red5安装目录下的red5.jar
* @author Administrator
*/
public class Application extends ApplicationAdapter{
/**
* 实现appStart和appStop两个必要的方法
**/
private IScope appScope;
/**clientDic用来保存连接的客户端用户信息,key为连接的用户名,value是对应的IConnection对象*/
private Map<String,IConnection> clientDic = new HashMap<String,IConnection>();
public boolean appStart()
{
return true;
}
public void appStop()
{
}
@Override
/***
* 连接逻辑处理
*
*/
public synchronized boolean connect(IConnection conn, IScope scope,
Object[] params) {
if(params.length == 0){
rejectClient("Must pass the login name.");
}
/**获取第一个参数,第一个参数传用户名*/
String loginname = params[0].toString();
/**当前连接对象设置一个用户名属性*/
conn.setAttribute("UserName", loginname);
/**判断当前用户名是否存在HashMap里,如果存在则拒绝连接*/
if(!clientDic.keySet().contains(loginname))
{
clientDic.put(loginname, conn);
/**
* 获取服务器端SharedObject对象,当SharedObject对象为空时创建,并将用户列表保存到该对象
* 每次执行连接时,维护更新用户列表
* */
ISharedObject so = this.getSharedObject(scope, "UserSO");
if(so==null)
{
this.createSharedObject(scope, "UserSO",true);
so = getSharedObject(scope, "UserSO");
}
so.setAttribute("userlist", clientDic.keySet().toArray());
}
else
{
this.rejectClient("The same user has logged in already.");
}
return super.connect(conn, scope, params);
}
@Override
/***
* 断开连接逻辑处理
*
*/
public synchronized void disconnect(IConnection conn, IScope scope) {
/**判断断开的用户是否存在当前用户列表,如果存在就删除,然后更新用户列表*/
String loginname = conn.getAttribute("UserName").toString();
if(loginname != null)
{
if(clientDic.keySet().contains(loginname))
{
clientDic.remove(loginname);
ISharedObject so = this.getSharedObject(scope, "UserSO");
if(so != null)
{
so.setAttribute("userlist", clientDic.keySet().toArray());
}
}
}
super.disconnect(conn, scope);
}
}
客户端代码:
package
{
import flash.display.Sprite;
import flash.events.NetStatusEvent;
import flash.events.SyncEvent;
import flash.net.NetConnection;
import flash.net.SharedObject;
public class ConnectTest extends Sprite
{
private var nc:NetConnection;
private var so:SharedObject;
public function ConnectTest()
{
nc = new NetConnection();
nc.addEventListener(NetStatusEvent.NET_STATUS,netStatus);
nc.connect("rtmp://127.0.0.1/firstapp","test1");//连接时地址不需要端口号
}
private function netStatus(e:NetStatusEvent):void
{
trace(e.info.code);
trace(e.info.level);
switch(e.info.code)
{
case "NetConnection.Connect.Success":
so = SharedObject.getRemote("UserSO",nc.uri,true);
so.addEventListener(SyncEvent.SYNC,onSync); //在服务器更新了远程共享对象后调度
so.connect(nc);
break;
default:
trace(e.info.application);
break;
}
}
private function onSync(e:SyncEvent):void
{
trace("event:"+e);
//遍历值改变的属性,changeList存着服务器端SharedObject对象属性变化的对象,其中每个元素都有一个code属性表明变化的类型
for(var i:Object in e.changeList)
{
var changeObj:Object = e.changeList[i];
trace("Change Code:"+changeObj.code);
switch(changeObj.code)
{
case "change":
trace("name:"+changeObj.name,"oldValue:"+changeObj.oldValue);
break;
}
}
}
}
}

广播信息
服务器端:
    在客户端,可以通过NetConnection对象的call方法来调用服务器端的某个方法,NetConnection对象通过call调用服务器方法里会传入一个字符串、一个Responder对象和其他参数,在服务器定义被调用的方法应有一个List<String>参数定义并且此方法必须是public类型。该方法需要实现以下逻辑为:获取当前共享的SharedObject;通过SharedObject广播信息到所有客户端。
    下面代码是服务器端方法获得当前共享的SharedObject对象并广播信息:
   public void sendMessage(List<String> params){
                //通过Red5获取当前的Connection对象和Scope
               IConnection conn = Red5.getConnectionLocal();    
               IScope scope = conn.getScope();
               ISharedObject so = getSharedObject(scope,"UserSO");//获取名字UserSO的服务器端SharedObject对象
         //sharedObject通过sendMessage方法调用所有客户端的receiveMsg方法广播信息,
        <span style="font-family:Microsoft YaHei UI;">//</span>客户端实现 receiveMsg方法接收信息并处理  <pre name="code" class="java">        so.sendMessage("receiveMsg",params);
 }

客户端代码:
function netStatus(event:NetStatusEvent):void{
   if(event.info.code == "NetConnection.Connect.Success"){
       var so:SharedObject = SharedObject.getRemote("UserSO",nc.uri,true);
       //<span style="font-family:Microsoft YaHei UI;">设置服务器端回调方法的对象</span>
       so.client = this;
       so.connect(nc);
   }
}
/<span style="font-family:Microsoft YaHei UI;">**
 * 被服务器端回调的方法
 * @params msg 信息
</span><span style="font-family:Microsoft YaHei UI;">public function receiveMessge(msg:String):void{
//逻辑处理
}
</span>

单播信息到特定用户
    单播信息的原理和多播类似,不同的是单播是通过服务器端调用客户端回调方法来完成而不是依靠SharedObject的同步机制来完成。
    与广播同样,在服务器端需要定义一个方法供客户端调用,客户端需要传递发送方、接收者、内容等信息。服务器端定义接收信息的方法如下:
 /**
* 接收客户端传递的调用信息
* @params params 客户端传递的参数数组,第一个参数是发送者名称,第二个参数是接收者名称,第三个参数是信息内容
*
*/
   public void sendPrvMessage(List<String> params) {
if(params.size() != 3){
  //做一些错误处理
return;
}
String sender = params.get(0);
String receiver = params.get(1);
String msg = params.get(2);
              //判断是否有接收者的连接
              if(clientdic.keySet().contains(receiver)){
                  IConnection conn = clientdic.get(receiver);
                  //要回调客户端的方法,需要使用接口类型IServiceCapableConnection
                  if(conn instanceof IServiceCapableConnectioin){
                    IServiceCapableConnection sc = (IServiceCapableConnection) conn;
                    //调用conn所连接的客户端的receivePrvMsg方法
                    sc.invoke("receivePrvMsg",new Object[]{sender,msg});
                  }
               }
}

客户端定义回调函数receivePrvMsg:
public function receivePrveMsg(sender:String,msg:String):void{
    //逻辑处理
}









<think> 我们被要求提供可用于打印的QML学习笔记。根据提供的引用资料,我们可以整理出一个结构化的学习笔记,包含QML的基本语法、结构、布局、信号和槽、函数属性等核心内容。同时,我们需要确保格式清晰,适合打印。 我们将按照以下结构组织笔记: 1. QML简介 2. QML基本结构 3. 导入模块 4. 对象声明与属性 5. 布局(锚布局) 6. 信号和槽 7. 函数属性(方法) 注意:由于是打印版,我们应避免使用代码块(但在文本中仍需清晰展示代码结构),并确保内容连贯。同时,我们将参考多个引用,并在适当位置添加引用标识。 开始整理: # QML学习笔记(可打印版) ## 1. QML简介 QML(Qt Meta-Object Language)是一种用于创建高度动态应用程序的多范式语言。使用QML,可以声明诸如UI组件之类的应用程序构建块,并设置各种属性来定义应用程序行为。应用程序行为可以通过JavaScript进一步编写脚本,JavaScript是该语言的一个子集。此外,QML大量使用Qt,这允许直接从QML应用程序访问Qt的类型和其他Qt功能[^1]。 ## 2. QML基本结构 QML文档的基本结构由元素名、属性和子元素组成: ``` 元素名 { 属性名1: 属性值1 属性名2: 属性值2 ... 子元素1 { ... } 子元素2 { ... } ... } ``` 例如: ```javascript import QtQuick 2.12 import QtQuick.Window 2.12 Window { width: 640 // 宽 height: 480 // 高 visible: true // 是否可见 title: qsTr("Hello World") // 标题 // 子元素1:矩形 Rectangle { width: 100 height: 100 color: "red" } // 子元素2:文本 Text { text: "Hello World" font.bold: true font.pixelSize: 24 color: "red" } } ``` 在这个例子中,`Window`是根元素,它包含两个子元素:`Rectangle`和`Text`[^2]。 ## 3. 导入模块 在QML中,使用`import`语句导入模块。可以导入: - 已注册类型的版本化命名空间(如`import QtQuick 2.0`) - 包含作为QML文档的类型定义的相对目录(如`import "../privateComponents"`) - JavaScript文件(如`import "somefile.js" as Script`) 示例: ```javascript import QtQuick 2.0 import QtQuick.LocalStorage 2.0 as Database import "../privateComponents" import "somefile.js" as Script ``` 导入的模块提供了在QML中使用的类型和功能[^3]。 ## 4. 对象声明与属性 在QML中,对象通过声明其类型并设置属性来创建。每个对象都可以包含属性、信号、方法等。属性可以是基本类型(如int, string, color)或复杂类型(如其他对象)。 ## 5. 布局(锚布局) QML使用锚(anchors)对元素进行布局。锚定是基础元素对象的基本属性,可以被所有的可视化QML元素使用。锚就像协议,比几何变化更强大。锚是相对关系的表达式,通常需要与其他元素搭配使用[^4]。 示例: ```javascript Rectangle { id: rect1 width: 100; height: 50; color: "blue" } Rectangle { id: rect2 width: 100; height: 50; color: "red" anchors.top: rect1.bottom // rect2的顶部锚定在rect1的底部 } ``` ## 6. 信号和槽 信号和槽机制用于对象之间的通信。当某个对象的特定事件发生时,会发出信号,而槽是响应该信号的函数。 在QML中,可以使用以下方式连接信号和槽: - 使用`on<Signal>`语法(如`onClicked: { ... }`) - 使用`Connections`对象 示例: ```javascript Button { id: button onClicked: { // 当按钮被点击时执行 console.log("Button clicked") } } ``` ## 7. 函数属性(方法) 对象类型的方法是可以调用以执行某些处理或触发更多事件的函数。方法可以连接到信号,以便在发出信号时自动调用该方法。 定义方法有两种方式: - 在C++中注册一个类的函数到QML类型系统。 - 在QML文档中直接使用函数声明语法。 语法: ```javascript function 函数名([参数1[, 参数2, ...]]) [: 返回类型] { // 函数体 } ``` 注意:与信号不同,方法参数类型不必声明(默认为`var`类型),但建议声明以提高性能和可维护性[^5]。 示例: ```javascript import QtQuick 2.0 Rectangle { id: rect width: 100 // 定义一个计算高度的方法 function calculateHeight(): real { return rect.width / 2; } height: calculateHeight() // 使用函数计算高度 } ``` ## 总结 QML是一种声明性语言,用于构建流畅的用户界面。它结合了JavaScript的灵活性和Qt的强大功能,使得开发动态应用变得简单高效。 --- [^1]: QT开发学习笔记,环境搭建、QML [^2]: QT学习笔记-qml开发 [^3]: QML学习笔记 [^4]: QML学习笔记【02】:QML快速入门 [^5]: QT中QML学习笔记2 §§相关问题§§ 1. QML中的锚布局(anchors)具体有哪些属性? 2. 如何在QML中导入和使用JavaScript文件? 3. 请解释QML中信号和槽的连接方式。 4. 如何优化QML应用的性能? </think> ### QML学习笔记(可打印版) #### **一、QML基础概念** 1. **QML定义** QML(Qt Meta-Object Language)是一种用于创建动态应用程序的多范式语言。它通过声明式语法构建UI组件,支持属性绑定和JavaScript脚本,可直接访问Qt底层功能[^1]。 **核心特点**: - 组件化声明 - 属性驱动行为 - JavaScript脚本扩展 2. **基本结构** ```qml 元素名 { 属性名1: 属性值1 属性名2: 属性值2 子元素 { // 嵌套定义 } } ``` **示例**: ```qml Window { width: 640 // 宽度属性 height: 480 // 高度属性 visible: true // 可见性 Rectangle { // 子元素 color: "red" width: 100 } } ```[^2] --- #### **、核心语法详解** 1. **导入模块** - 标准模块:`import QtQuick 2.12` - JavaScript文件:`import "utils.js" as Script` - 本地目录:`import "../components"` **注意**:版本号需与Qt安装版本匹配[^3]。 2. **属性声明** | 属性类型 | 示例 | |----------------|--------------------------| | 基础属性 | `width: 100` | | 绑定属性 | `height: width * 0.5` | | 颜色属性 | `color: "#FF0000"` | | 文本属性 | `text: qsTr("Hello")` | 3. **锚布局(Anchors)** - 相对定位元素的核心方法[^4]: ```qml Rectangle { anchors.centerIn: parent // 居中于父元素 anchors.left: sibling.right // 锚定到兄弟元素右侧 } ``` --- #### **三、进阶功能** 1. **信号与槽** - **信号定义**: ```qml signal mySignal(string message) ``` - **槽连接**: ```qml Button { onClicked: parent.mySignal("Clicked!") // 触发信号 } ``` 2. **函数属性** 动态计算方法: ```qml Rectangle { id: rect function calculateArea() { return width * height } Text { text: "面积:" + calculateArea() } } ```[^5] --- #### **四、开发实践** 1. **组件化设计** - 创建自定义组件 `MyButton.qml`: ```qml // 文件:MyButton.qml Rectangle { signal clicked Text { text: "按钮" } MouseArea { onClicked: parent.clicked() } } ``` - 主文件调用: ```qml MyButton { onClicked: console.log("按钮被点击") } ``` 2. **性能优化** - 避免过度嵌套布局 - 使用`Loader`动态加载组件 - 启用QML缓存机制(`qmlcache`) --- #### **引用来源** [^1]: QT开发学习笔记,环境搭建、QML [^2]: QT学习笔记-qml开发 [^3]: QML学习笔记 [^4]: QML学习笔记【02】:QML快速入门 [^5]: QT中QML学习笔记2 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值