red5源码分析—客户端连接
本博文开始分析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 {
private ConcurrentLinkedQueue<IMessage> frameBuffer = new ConcurrentLinkedQueue<IMessage>();
String host = "127.0.0.1";
String app = "red5test";
int port = 1935;
public RtmpClientTest() {
super();
Map<String, Object> map = makeDefaultConnectionParams(host,
1935, "red5test");
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) {
ObjectMap<?, ?> map = (ObjectMap<?, ?>) notify.getCall().getArguments()[0];
String code = (String) map.get("code");
if (StatusCodes.NS_PUBLISH_START.equals(code)) {
IMessage message = null;
while ((message = frameBuffer.poll()) != null) {
this.publishStreamData(streamId, message);
}
} else if (StatusCodes.NS_UNPUBLISHED_SUCCESS.equals(code)) {
}
}
@Override
public void connectionOpened(RTMPConnection conn, RTMP state) {
super.connectionOpened(conn, state);
}
public static void main(String[] args) {
new RtmpClientTest();
}
}
RtmpClientTest实现的三个接口INetStreamEventHandler、IPendingServiceCallback、IEventDispatcher和回调函数有关,后面的章节会分析到。
首先来看RtmpClientTest的构造函数,其父类RTMPClient的构造函数如下
public RTMPClient() {
ioHandler = new RTMPMinaIoHandler();
ioHandler.setHandler(this);
}
red5客户端使用mina框架来封装Java Nio,关于mina框架的源码分析请查看博主的mina源码分析系列文章。这里有两个handler,RTMPMinaIoHandler和mina框架有关,另一个handler就是RTMPClient自身,因为其继承自BaseRTMPClientHandler,BaseRTMPClientHandler和业务有关。
回到RtmpClientTest构造函数,接下来调用connect进行连接,connect函数实现在RTMPClient的父类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);
}
connect函数一开始作了一些简单的设置,最后通过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函数。connect函数会使用mina框架与服务器建立连接,下一章会分析服务器如何处理客户端的连接请求。当与服务器建立完连接(TCP连接)时,根据mina框架的源码,会回调mina框架中IoHandler的处理函数,也即前面注册的RTMPMinaIoHandler的sessionCreated和sessionOpened函数,下面依次来看。
一. sessionCreated
sessionCreated的代码如下,
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");
if (!StringUtils.isEmpty(swfUrl)) {
outgoingHandshake.initSwfVerification(swfUrl);
}
}
session.setAttribute(RTMPConnection.RTMP_HANDLER, handler);
handler.setConnection((RTMPConnection) conn);
}
sessionCreated函数首先向mina框架中添加一个过滤器RTMPEIoFilter,该过滤器用来处理RTMP协议的握手过程,具体的RTMP协议可以从网上下载。sessionCreated接着创建一个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
再来看RTMPMinaIoHandler的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);
}
这里根据从session中获得刚刚在sessionCreated中创建的OutboundHandshake,调用其generateClientRequest1函数生成第一次握手请求的数据,通过write函数发送给服务器。generateClientRequest1函数和具体的协议相关,这里就不继续往下看了。
总结一下,本章分析了如何创建一个RTMPClient并建立与服务器的TCP连接,然后发送第一次握手请求开始与服务器建立RTMP连接,下一章开始分析red5服务器端对应的连接函数。