red5服务器端也采用了mina框架进行底层Socket的封装。red5使用Spring管理相关的bean,下面简单说明一下red5 standalone的启动流程。
main函数在org.red5.server.Bootstrap中,该类加载org.red5.server.Launcher,Launcher通过Spring解析red5.xml文件,red5.xml文件会继续根据red5-common.xml和red5-core.xml创建相应的bean,并根据red5.globals文件创建一个GlobalScope,后面会提到。
本文从red5-core.xml文件中构造的RTMPMinaTransport说起,
<bean id="rtmpTransport" class="org.red5.server.net.rtmp.RTMPMinaTransport" init-method="start" destroy-method="stop">
<property name="ioHandler" ref="rtmpMinaIoHandler" />
<property name="addresses">
<list>
<value>${rtmp.host}:${rtmp.port}</value>
</list>
</property>
<property name="ioThreads" value="${rtmp.io_threads}" />
<property name="sendBufferSize" value="${rtmp.send_buffer_size}" />
<property name="receiveBufferSize" value="${rtmp.receive_buffer_size}" />
<property name="trafficClass" value="${rtmp.traffic_class}" />
<property name="backlog" value="${rtmp.backlog}" />
<property name="tcpNoDelay" value="${rtmp.tcp_nodelay}" />
<property name="keepAlive" value="${rtmp.tcp_keepalive}" />
<property name="thoughputCalcInterval" value="${rtmp.thoughput_calc_interval}" />
<property name="enableDefaultAcceptor" value="${rtmp.default_acceptor}" />
<property name="initialPoolSize" value="${rtmp.initial_pool_size}" />
<property name="maxPoolSize" value="${rtmp.max_pool_size}" />
<property name="maxProcessorPoolSize" value="${rtmp.max_processor_pool_size}" />
<property name="executorKeepAliveTime" value="${rtmp.executor_keepalive_time}" />
<property name="minaPollInterval" value="${jmx.mina.poll.interval}" />
<property name="enableMinaMonitor" value="${jmx.mina.monitor.enable}" />
<property name="enableMinaLogFilter" value="${mina.logfilter.enable}" />
</bean>
根据bean的配置,spring会调用RTMPMinaTransport的start函数进行初始化,
public void start() throws Exception {
initIOHandler();
IoBuffer.setUseDirectBuffer(!useHeapBuffers);
if (useHeapBuffers) {
IoBuffer.setAllocator(new SimpleBufferAllocator());
}
if (enableDefaultAcceptor) {
acceptor = new NioSocketAcceptor(ioThreads);
} else {
SimpleIoProcessorPool<NioSession> pool = new SimpleIoProcessorPool<NioSession>(NioProcessor.class, maxProcessorPoolSize);
executor = new ThreadPoolExecutor(initialPoolSize, maxPoolSize, executorKeepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(Short.MAX_VALUE));
acceptor = new NioSocketAcceptor(executor, pool);
}
if (enableMinaLogFilter) {
DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
LoggingFilter logFilter = new LoggingFilter(RTMPMinaTransport.class);
chain.addLast("logger", logFilter);
}
acceptor.setCloseOnDeactivation(true);
acceptor.setHandler(ioHandler);
acceptor.setBacklog(backlog);
SocketSessionConfig sessionConf = acceptor.getSessionConfig();
sessionConf.setReuseAddress(true);
sessionConf.setTcpNoDelay(tcpNoDelay);
sessionConf.setSendBufferSize(sendBufferSize);
sessionConf.setReceiveBufferSize(receiveBufferSize);
sessionConf.setMaxReadBufferSize(receiveBufferSize);
sessionConf.setThroughputCalculationInterval(thoughputCalcInterval);
sessionConf.setReaderIdleTime(readerIdleTime);
sessionConf.setKeepAlive(keepAlive);
if (trafficClass == -1) {
} else {
sessionConf.setTrafficClass(trafficClass);
}
acceptor.setReuseAddress(true);
try {
Set<InetSocketAddress> socketAddresses = new HashSet<InetSocketAddress>();
for (String addr : addresses) {
if (addr.indexOf(':') != -1) {
String[] parts = addr.split(":");
socketAddresses.add(new InetSocketAddress(parts[0], Integer.valueOf(parts[1])));
} else {
socketAddresses.add(new InetSocketAddress(addr, 1935));
}
}
acceptor.bind(socketAddresses);
String cName = this.getClass().getName();
if (cName.indexOf('.') != -1) {
cName = cName.substring(cName.lastIndexOf('.')).replaceFirst("[\\.]", "");
}
if (enableMinaMonitor) {
stats = new IoServiceStatistics((AbstractIoService) acceptor);
stats.setThroughputCalculationInterval(minaPollInterval);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try {
serviceManagerObjectName = new ObjectName("org.red5.server:type=RTMPMinaTransport");
mbs.registerMBean(new StandardMBean(this, RTMPMinaTransportMXBean.class, true), serviceManagerObjectName);
} catch (Exception e) {
}
}
} catch (Exception e) {
}
}
这段代码虽然较长,但其实是mina框架的初始化,initIOHandler用于初始化mina框架中的handler,但根据red5-core.xml,其已经被初始化为RTMPMinaIoHandler,并设置其handler为RTMPHandler,和上一章分析类似,RTMPMinaIoHandler属于mina框架调用,RTMPHandler属于RTMP业务调用,两个handler执行的环境不同。
<bean id="rtmpHandler" class="org.red5.server.net.rtmp.RTMPHandler">
<property name="server" ref="red5.server" />
<property name="statusObjectService" ref="statusObjectService" />
</bean>
<bean id="rtmpMinaIoHandler" class="org.red5.server.net.rtmp.RTMPMinaIoHandler">
<property name="handler" ref="rtmpHandler" />
</bean>
再往下设置了IoBuffer,构造NioSocketAcceptor用于监听客户端连接,并对其进行设置,然后就调用其bind开始监听了。
当有客户端连接到来时,mina框架回调RTMPMinaIoHandler的sessionCreated和sessionOpened函数,下面来看,
public void sessionCreated(IoSession session) throws Exception {
session.getFilterChain().addFirst("rtmpeFilter", new RTMPEIoFilter());
RTMPMinaConnection conn = createRTMPMinaConnection();
conn.setIoSession(session);
conn.setHandler(handler);
session.setAttribute(RTMPConnection.RTMP_SESSION_ID, conn.getSessionId());
session.setAttribute(RTMPConnection.RTMP_HANDSHAKE, new InboundHandshake());
}
首先添加了RTMPEIoFilter过滤器用于握手,然后通过createRTMPMinaConnection构造了一个RTMPMinaConnection,然后将RTMPHandler设置进去,并向session中添加InboundHandshake用于服务器端的握手。
下面来看sessionOpened,
public void sessionOpened(IoSession session) throws Exception {
String sessionId = (String) session.getAttribute(RTMPConnection.RTMP_SESSION_ID);
RTMPConnManager connManager = (RTMPConnManager) RTMPConnManager.getInstance();
session.setAttribute(RTMPConnection.RTMP_CONN_MANAGER, new WeakReference<IConnectionManager<RTMPConnection>>(connManager));
RTMPMinaConnection conn = (RTMPMinaConnection) connManager.getConnectionBySessionId(sessionId);
handler.connectionOpened(conn);
}
其实这里就能很明显地看出,客户端这段代码仅仅设置了RTMPMinaConnection。而这里服务端的代码设置了整个RTMPConnManager,并根据sessionId找到该RTMPMinaConnection。
最后调用了RTMPHandler的connectionOpened函数,该函数设置了握手等待的最长时间,这里就不往下看了。