本博文开始分析red5版本1.0.7的源码,详细分析从客户端到服务器的完整流程。
red5发展到现在,可以兼容很多协议,例如rtmp、rtmpt等等。本博文只分析rtmp协议,其他协议看看以后有没有时间研究吧。
red5的服务器启动有好几种方式,standalone、tomcat、jetty等等。本文只分析standalone的启动方式。客户端也一样,本博文分析使用red5 rtmpclient作为客户端的情况。
red5 client和server的下载地址如下
https://github.com/Red5
先看一段网上很常见的使用red5的客户端代码,
public class RtmpClientTest extends RTMPClient implements INetStreamEventHandler, IPendingServiceCallback, IEventDispatcher {
String host = "127.0.0.1";
String app = "chainGunSyncService";
int port = 1935;
public RtmpClientTest() {
super();
Map<String, Object> map = makeDefaultConnectionParams(host,
1935, "chainGunSyncService");
connect(host, 1935, map, this);
}
@Override
public void dispatchEvent(IEvent arg0) {
}
@Override
public void resultReceived(IPendingServiceCall call) {
Object result = call.getResult();
if (result instanceof ObjectMap) {
if ("connect".equals(call.getServiceMethodName())) {
createStream(this);
}
} else {
if ("createStream".equals(call.getServiceMethodName())) {
if (result instanceof Integer) {
Integer streamIdInt = (Integer) result;
// int streamId = streamIdInt.intValue();
// publish(streamId, "testgio2", "live", this);
invoke("getRoomsInfo", this);
} else {
disconnect();
}
} else if ("getRoomsInfo".equals(call.getServiceMethodName())) {
ArrayList<String> list = (ArrayList<String>) result;
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
}
@Override
public void onStreamEvent(Notify arg0) {
}
@Override
public void connectionOpened(RTMPConnection conn, RTMP state) {
super.connectionOpened(conn, state);
}
public static void main(String[] args) {
new RtmpClientTest();
}
}
首先来看RtmpClientTest的构造函数,其父类RTMPClient的构造函数如下
public RTMPClient() {
ioHandler = new RTMPMinaIoHandler();
ioHandler.setHandler(this);
}
red5 Client使用了mina框架来封装Java Nio,关于mina框架的源码分析请查看博主的其他文章吧。这里有两个handler,RTMPMinaIoHandler暂时命名为框架handler吧,和mina框架有关,另一个handler就是RTMPClient自身,因为其继承自BaseRTMPClientHandler,暂时命名为业务handler吧。
回到RtmpClientTest构造函数,接下来调用connect进行连接,connect函数实现在刚才提到的其父类的父类BaseRTMPClientHandler中,
public void connect(String server, int port, Map<String, Object> connectionParams, IPendingServiceCallback connectCallback) {
connect(server, port, connectionParams, connectCallback, null);
}
public void connect(String server, int port, Map<String, Object> connectionParams, IPendingServiceCallback connectCallback, Object[] connectCallArguments) {
this.connectionParams = connectionParams;
this.connectArguments = connectCallArguments;
if (!connectionParams.containsKey("objectEncoding")) {
connectionParams.put("objectEncoding", 0);
}
this.connectCallback = connectCallback;
startConnector(server, port);
}
这里就是一些简单的赋值,关键是调用startConnector进行连接,startConnector定义在RTMPClient中,
protected void startConnector(String server, int port) {
socketConnector = new NioSocketConnector();
socketConnector.setHandler(ioHandler);
future = socketConnector.connect(new InetSocketAddress(server, port));
future.addListener(new IoFutureListener<ConnectFuture>() {
public void operationComplete(ConnectFuture future) {
try {
session = future.getSession();
} catch (Throwable e) {
socketConnector.dispose(false);
handleException(e);
}
}
});
future.awaitUninterruptibly(CONNECTOR_WORKER_TIMEOUT);
}
这里主要构造了一个NioSocketConnector,并调用其connect函数。再往下就是mina框架要处理的事情了。当与服务器建立完连接(TCP)时,会回调mina框架中IoHandler的处理函数,也即RTMPMinaIoHandler的sessionCreated和sessionOpened函数,下面依次来看,
public void sessionCreated(IoSession session) throws Exception {
session.getFilterChain().addFirst("rtmpeFilter", new RTMPEIoFilter());
RTMPMinaConnection conn = createRTMPMinaConnection();
conn.setIoSession(session);
session.setAttribute(RTMPConnection.RTMP_SESSION_ID, conn.getSessionId());
OutboundHandshake outgoingHandshake = new OutboundHandshake();
session.setAttribute(RTMPConnection.RTMP_HANDSHAKE, outgoingHandshake);
if (enableSwfVerification) {
String swfUrl = (String) handler.getConnectionParams().get("swfUrl");
log.debug("SwfUrl: {}", swfUrl);
if (!StringUtils.isEmpty(swfUrl)) {
outgoingHandshake.initSwfVerification(swfUrl);
}
}
session.setAttribute(RTMPConnection.RTMP_HANDLER, handler);
handler.setConnection((RTMPConnection) conn);
}
sessionCreated首先向mina框架中添加一个RTMPEIoFilter,该过滤器用来处理RTMP协议的握手过程,具体的RTMP协议可以从网上下载。接着创建一个RTMPMinaConnection并进行相应的设置,
protected RTMPMinaConnection createRTMPMinaConnection() {
return (RTMPMinaConnection) RTMPConnManager.getInstance().createConnection(RTMPMinaConnection.class);
}
RTMPConnManager是一个单例,直接看其createConnection函数,
public RTMPConnection createConnection(Class<?> connCls) {
RTMPConnection conn = null;
if (RTMPConnection.class.isAssignableFrom(connCls)) {
try {
conn = createConnectionInstance(connCls);
connMap.put(conn.getSessionId(), conn);
} catch (Exception ex) {
}
}
return conn;
}
public RTMPConnection createConnectionInstance(Class<?> cls) throws Exception {
RTMPConnection conn = null;
if (cls == RTMPMinaConnection.class) {
conn = (RTMPMinaConnection) cls.newInstance();
} else if (cls == RTMPTClientConnection.class) {
conn = (RTMPTClientConnection) cls.newInstance();
} else {
conn = (RTMPConnection) cls.newInstance();
}
conn.setMaxHandshakeTimeout(maxHandshakeTimeout);
conn.setMaxInactivity(maxInactivity);
conn.setPingInterval(pingInterval);
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setDaemon(true);
executor.setMaxPoolSize(1);
executor.setQueueCapacity(executorQueueCapacity);
executor.initialize();
conn.setExecutor(executor);
return conn;
}
这里就是构造了一个RTMPMinaConnection实例,设置其ThreadPoolTaskExecutor,并添加进connMap中。注意每个RTMPMinaConnection的SessionId是随机生成的。
回到sessionCreated中,接下来设置刚刚构造的RTMPMinaConnection,以及其SessionId,然后构造OutboundHandshake用于RTMP的握手,握手结束后OutboundHandshake将会从session中移除,最后设置handler和RTMPMinaConnection。
再来看sessionOpened,
public void sessionOpened(IoSession session) throws Exception {
super.sessionOpened(session);
RTMPHandshake handshake = (RTMPHandshake) session.getAttribute(RTMPConnection.RTMP_HANDSHAKE);
IoBuffer clientRequest1 = ((OutboundHandshake) handshake).generateClientRequest1();
session.write(clientRequest1);
}
这里根据Id从session中获得在sessionCreated中构造的OutboundHandshake,调用其generateClientRequest1函数构造第一次握手的数据,通过write函数发送给服务器。generateClientRequest1函数和具体的协议相关,这里就不继续往下看了。
下一章开始分析服务器端相应的连接函数。