原生BBB系统框架介绍
说明分析基于bigbluebutton- 2.2-beta11版本,已忽略系统中flash的部分,只关注H5的实现。
BBB系统各模块介绍:
分析:服务器端存在多个模块,各模块之前通过redis pub通信,通信模型是基于事件的发布订阅机制,各模块订阅自己的通道 ,发布事件时发布到相应的通道。
注意:websocket的连接不是一有一条tcp直连,而是经过了nginx有两段tcp连接。
各模块在系统中的功能和作用:
- Bbb-html5模块:meteor客户端:负责业务的UI呈现,与用户交互实现业务场景的切换,与服务端交互实现各业务场景功能。与信令服务器交互推拉音视频流,与h5服务器交互申请改变数据库实现各子业务功能(投票,白板,聊天等),通过调用web-api实现文件上传,下载,转换等。
meteor服务端:nodejs进程,meteor工程服务端,通过websocket接收客户端请求,将相应请求分发出去并监听事件,改变mongo数据库并同步到客户端。
- Nginx模块:根据http/https 中请求中的url将请求分发到对应的模块,分布式反射代理服务,静态htmlweb服务。
- Bbb-web模块:对外提供功能API,负责会议室管理,用户加入会议,文件管理(文件上传,下载,转换成幻灯片格式(通过调用第三方软件 实现转换 png,swf等))。
- Bbb-akka-apps: apps 的作用是沟通系统中各模块,与bbb-web , bbb-html5模块共同实现房间,用户及用户行为的管理。例如bbb-html5的各请求均发送到bbb-apps模块,bbb-apps再根据事件作数据合法,权限检测,通过后不需要第三方软件协助的事件(例如聊天,投票等功能)再通知到bbb-html5,需要第三方软件协助(例如静音功能需要转发等freeswitch)转发到相应模块再收到相应事件回应后再通知到html5模块。
- 信令服务器模块,多个node js进程,整体功能就是媒体流的控制,客户端通过与信令服务器交互就可以实现流的推拉。它是通过控制 kurento 或freeswitch来最先实现流的转发。
- Kurento server模块,通过rtp,webrtc等技术实现流的转发。
- Bbb-akka-fsel ,通过sip信令控制freeswitch控制声音流(静音等功能),得到音频流状态(谁正在说话功能)
- Freeswitch。 音频会议室管理,用户加入,退出,静音等。 都是控制音频流的分发与合成来实现的。
- Bbb-transcode模块: 通过调用ffmpeg实现流转码功能,流的封包格式转换等。
例如 rtmp 转rtp , rtp 转rtmp等。
- bbb-html5模块通过mongod保存数据。
- Redis-server :用于实现进程音的通信,通信模型是基于事件的发布订阅机制,各模块订阅自己的通道 ,发布事件时发布到相应的通道。
- Bbb-webhooks模块,可以将系统中的各种事件通过http post的形式发送出去,开发者指定一个自己的url ,就能拿到系统中的所有事件了。
- Etherpad模块,开源的共享笔记应用,实现bbb系统中共享笔记的功能。
- Bbb-demo模块,简单的demo jsp页面,通过调用web-api实现房间的创建和用户的加入并跳转到 bbb-html5页面中去。
- Bbb-record-playback:录制与回放模块,通过对bbb系统中所有事件作处理,对录下的媒体流作处理,在会议结束后通过UI直观的方式来展示会议过程中所有发生的事件(包括媒体部分)。
Ps时看到的BBB进程:
bigblue+ java bbb-akaa-apps
freeswitch
bigblue+ java bbb-akaa-fsel
bigblue+ java bbb-akaa-transcode
root nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
tomcat7 /usr/lib/jvm/default-java/bin/java
bigblue+ /bin/bash /usr/share/bbb-web/run-prod.sh
etherpad /usr/bin/node node_modules/ep_etherpad-lite/node/server.js
bigblue+ java -Dgrails.env=prod -Dserver.port=8090
kurento /usr/bin/kurento-media-server
redis /usr/bin/redis-server 127.0.0.1:6379
mongodb /usr/bin/mongod --config /usr/share/meteor/bundle/mongo-ramdisk.conf
bigblue+ /usr/lib/libreoffice/program/soffice.bin--accept=socket
bigblue+ /usr/bin/node server.js
bigblue+ /usr/bin/node ./lib/mcs-core/process.js ./lib/mcs-cor
bigblue+ /usr/bin/node ./lib/screenshare/ScreenshareProcess ./
bigblue+ /usr/bin/node ./lib/video/VideoProcess.js ./lib/video
bigblue+ /usr/bin/node ./lib/audio/AudioProcess.js ./lib/audio
监听的二级目录名和商端口:
/html5client 3000
"^\/pad\/p\/(\w+)$" , /pad , /pad/socket.io , /static 9001 etherpad
/playback/presentation /var/bigbluebutton/index
/presentation /var/bigbluebutton/published/index
location /playback/presentation/playback.html {
return 301 /playback/presentation/0.81/playback.html?$query_string;
/ws 7443
/bigbluebutton 8090
/bbb-webrtc-sfu 3008
/demo 8080
…
BBB系统各模块总结:
通过模块图可以看到,bbb本身模块众多,还基于一些第三方软件 。实际上bbb系统每个模块本身的代码量并不大。 核心功能应该是会议室管理,人员管理,音视频通信,所以这些主要是分析 bbb-html5,bbb-web,bbb-akka-apps,bbb-webrtc-sfu 以及了解第三方软件 kruento,freeswitch。
bbb-akka-apps 模块分析:
在分析这个模块时,首先找到这个模块程序的入口程序 ,它是 Boot.scala中的 Boot. 这是由SprintBoot这个框架决定的。
在分析Boot之前 ,要先了解akka中的 actor 和 bus 的概念。
object Boot extends App with SystemConfiguration {
implicit val system = ActorSystem("bigbluebutton-apps-system")
implicit val executor = system.dispatcher
val logger = Logging(system, getClass)
val eventBus = new InMsgBusGW(new IncomingEventBusImp())
val outBus2 = new OutEventBus2
val recordingEventBus = new RecordingEventBus
val outGW = new OutMessageGatewayImp(outBus2)
val redisPublisher = new RedisPublisher(system, "BbbAppsAkkaPub")
val msgSender = new MessageSender(redisPublisher)
val redisRecorderActor = system.actorOf(RedisRecorderActor.props(system), "redisRecorderActor")
recordingEventBus.subscribe(redisRecorderActor, outMessageChannel)
val incomingJsonMessageBus = new IncomingJsonMessageBus
val bbbMsgBus = new BbbMsgRouterEventBus
val fromAkkaAppsMsgSenderActorRef = system.actorOf(FromAkkaAppsMsgSenderActor.props(msgSender))
val analyticsActorRef = system.actorOf(AnalyticsActor.props())
outBus2.subscribe(fromAkkaAppsMsgSenderActorRef, outBbbMsgMsgChannel)
outBus2.subscribe(redisRecorderActor, recordServiceMessageChannel)
outBus2.subscribe(analyticsActorRef, outBbbMsgMsgChannel)
bbbMsgBus.subscribe(analyticsActorRef, analyticsChannel)
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW), "bigbluebutton-actor")
eventBus.subscribe(bbbActor, meetingManagerChannel)
val redisMessageHandlerActor = system.actorOf(ReceivedJsonMsgHandlerActor.props(bbbMsgBus, incomingJsonMessageBus))
incomingJsonMessageBus.subscribe(redisMessageHandlerActor, toAkkaAppsJsonChannel)
val redisSubscriberActor = system.actorOf(AppsRedisSubscriberActor.props(system, incomingJsonMessageBus), "redis-subscriber")
}
首先看Boot的最后一句:它在构造 AppsRedisSubscriberActor 时会调用 subscript()订阅相关的通道,收到外部的事件,然后传入apps内部。
具体细节:
AppsRedisSubscriberActor 继承了 RedisSubscripberProvider(位置在bbb-common-message/sr/main/scala/org/bigbluebutton/common2/redis/RedisSubScriberProvider.scala)
看 RedisSubscriterProvider的订阅方法:
可以看到它是用redis的subscripe 订阅相关的通道()。
toAkkaTranscodeRedisChannel,fromVoiceConfRedisChannel,toAkkaAppsJsonChannel
从名称可以看到分别是 转码相关,声音相关,和指发到这模块的通道。
在收到消息后利用incomingJsonMessageBus将消息发布出去,现在就到了apps模块内部来处理这些消息了。 代码如图所示。
再分析处理接着处理消息的 actor ReceivedJsonMsgHandlerActor.
这个actor 也只是消息根据类型分发到eventBus的 不同通道中,在用户真正进入到会议室之前 的发布到了meetingManagerChanne通道,比如会议室的创建,用户进入时的验证等消息,用户进入到会议室之后的操作发布到了以会议室ID命名的通道。比如用户聊天 ,投票等消息。 还有声音相关的被分发 voiceConf 的通道。
多发了一个 anylyticsEvent事件,这个事件主要是用来记录的,处理这个事件的actor只是将事件存入到Log文件中,只是用于记录事件log. 这个事件先不做分析。
消息现在到了eventbus ,接着往下分析eventbus中消息的处理。
在处理创建会议室时又使evetbus订阅了 会议室id,voiceCOnf,screensahreConf通道。 也就是说在用户进入后的所有操作的消息处理都在 RunningMeeting 的 MeetingActor 中。
消息的处理过程大多数是权限检测,数据检测等,然后通过后在apps模块新增相应的数据。 然后再将处理结果 发送出去。
再来分析发送消息出去的过程:
通过outGW 发送到 outBus2 的 meetingManagerChannel,然后再到 FromAkkaAppsMsgSenderActor 通过 msgSender 发送到redis 中去。
房间创建,用户加入过程详细分析:
房间创建,用户加入时序图:
分析源码时整理的函数调用流程图:
从时序图中可以看到完成房间创建,用户加入是bbb-web,bbb-apps,html5三个模块共同协助的结果。他们之前通信消息应该有统一的格式,实际上bbb系统消息都有统一的格式,所有各模块之间才能顺利沟通,消息的创建是由bbb-common-web,bbb-common-message的代码完成的。
事件消息数据示例:
注册用户请求消息:
{
"payload": {
"name": "RED",
"role": "MODERATOR",
"user_id": "rg74zt7x78oe",
"meeting_id": "183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1397936286394",
"external_user_id": "rg74zt7x78oe"
},
"header": {
"timestamp": 1193516905118123,
"name": "register_user_request"
}
}
用户加入消息
{
"payload": {
"recorded": false,
"meeting_id": "183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1397936286394",
"user": {
"voice_user": {
"talking": false,
"joined": false,
"muted": false,
"caller_id_name": "RED",
"user_id": "rg74zt7x78oe",
"locked": false,
"web_user_id": "rg74zt7x78oe",
"caller_id_num": "RED"
},
"name": "RED",
"phone_user": false,
"has_stream": false,
"webcam_stream": "",
"role": "MODERATOR",
"user_id": "rg74zt7x78oe",
"locked": false,
"raise_hand": false,
"presenter": false,
"external_user_id": "rg74zt7x78oe"
}
},
"header": {
"timestamp": 1193517200604687,
"name": "user_joined_message"
}
}
从消息格式可以看出 header中标明了消息类型,payload中是消息的数据。
从时序图的分析中可以看到 bbb-web,bbb-apps,bbb-html5模块都存了会议,用户相关的所有数据,bbb-web,bbb-apps是存放在内存中,bbb-html5是存放在mongodb,并放入了内存中。
bbb-apps是处理各种消息的核心,所有的消息都会通过apps,apps还会将消息保存到redisdb中,可以用户之后回放会议使用。 Bbb-apps可以说是所有功能的处理中心,因为所有事件都需要经过apps确认,apps会对这些事件进行权限,是否合理,或者转发到相应模块去处理。整套BBB系统是通过消息来携同各个模块来转起来,所以bbb-apps应该是消息控制中心。