red5源码分析—客户端握手
在《red5源码分析—1》中提到过,客户端会在RTMPMinaIoHandler的sessionOpened函数中执行OutboundHandshake的generateClientRequest1函数生成第一次RTMP握手请求,在建立TCP连接之后,开始建立RTMP连接。
另外,根据《red5源码分析—1》的分析,客户端的sessionCreate函数向mina框架添加了RTMPEIoFilter过滤器,用来与服务器之间进行RTMP协议的握手,现在来看看这个过滤器是怎么处理RTMP协议的握手过程的。在mina框架建立玩TCP连接后,后续的数据会直接经过各个过滤器的messageReceived函数,因此,下面来看RTMPEIoFilter的messageReceived函数,
public void messageReceived(NextFilter nextFilter, IoSession session, Object obj) throws Exception {
String sessionId = (String) session.getAttribute(RTMPConnection.RTMP_SESSION_ID);
RTMPMinaConnection conn = (RTMPMinaConnection) RTMPConnManager.getInstance().getConnectionBySessionId(sessionId);
if (conn == null) {
}
RTMP rtmp = conn.getState();
final byte connectionState = conn.getStateCode();
IoBuffer message = (IoBuffer) obj;
OutboundHandshake handshake = null;
switch (connectionState) {
case RTMP.STATE_CONNECTED:
...
case RTMP.STATE_CONNECT:
...
case RTMP.STATE_HANDSHAKE:
...
case RTMP.STATE_ERROR:
case RTMP.STATE_DISCONNECTING:
case RTMP.STATE_DISCONNECTED:
break;
default:
throw new IllegalStateException("Invalid RTMP state: " + connectionState);
}
}
RTMPEIoFilter的messageReceived首先获得sessionId,然后根据该sessionId从RTMPConnManager获得RTMPMinaConnection,接着从该RTMPMinaConnection获得该连接的状态,一共有6种状态,STATE_CONNECTED表示连接完成,STATE_CONNECT表示正在连接(握手的第一状态),STATE_HANDSHAKE表示正在握手(握手的第二状态),STATE_ERROR表示发生错误,STATE_DISCONNECTING表示正在关闭连接,STATE_DISCONNECTED表示已关闭连接,RTMP的握手协议只和前三种状态有关,下面一一来看。
一. 状态STATE_CONNECT
RTMPMinaConnection在创建之后,默认的状态就为STATE_CONNECT,因此在《red5源码分析—1》中的sessionOpend中发送完第一次握手请求给服务器并返回时,这里的状态就为STATE_CONNECT。下面来看,
public void messageReceived(NextFilter nextFilter, IoSession session, Object obj) throws Exception {
...
switch (connectionState) {
case RTMP.STATE_CONNECTED:
...
case RTMP.STATE_CONNECT:
handshake = (OutboundHandshake) session.getAttribute(RTMPConnection.RTMP_HANDSHAKE);
handshake.addBuffer(message);
int s0s1Size = handshake.getBufferSize();
if (s0s1Size >= (Constants.HANDSHAKE_SIZE + 1)) {
int handshakeType = handshake.getHandshakeType();
if (handshakeType == 0) {
handshake.setHandshakeType(RTMPConnection.RTMP_NON_ENCRYPTED);
rtmp.setEncrypted(handshake.useEncryption());
}
IoBuffer buf = handshake.getBufferAsIoBuffer();
byte connectionType = buf.get();
if (handshake.getHandshakeType() != connectionType) {
}
byte[] dst = new byte[Constants.HANDSHAKE_SIZE];
buf.get(dst);
int remaining = buf.remaining();
if (remaining > 0) {
handshake.addBuffer(buf);
}
IoBuffer c2 = handshake.decodeServerResponse1(IoBuffer.wrap(dst));
if (c2 != null) {
conn.getState().setState(RTMP.STATE_HANDSHAKE);
session.write(c2);
if (handshake.getBufferSize() >= Constants.HANDSHAKE_SIZE) {
buf.clear();
buf = handshake.getBufferAsIoBuffer();
if (handshake.decodeServerResponse2(buf)) {
} else {
}
completeConnection(session, conn, rtmp, handshake);
}
} else {
conn.close();
}
}
break;
case RTMP.STATE_HANDSHAKE:
...
case RTMP.STATE_ERROR:
case RTMP.STATE_DISCONNECTING:
case RTMP.STATE_DISCONNECTED:
break;
default:
throw new IllegalStateException("Invalid RTMP state: " + connectionState);
}
}
OutboundHandshake是在《red5源码分析—1》中当TCP连接建立后Mina框架调用sessionCreated时设置进session中的,下面的代码就和协议相关进行数据处理,如果收到了服务器的S0S1信息,就调用OutboundHandshake的decodeServerResponse1方法生成第二次握手信息并发送,然后将RTMPMinaConnection的状态设置成STATE_HANDSHAKE;如果收到了服务器的S0S1+S2信息,就直接判断为成功,并调用completeConnection函数,后面来看这个函数。
二. 状态STATE_HANDSHAKE
STATE_HANDSHAKE状态代表第二次握手状态,进入该状态并收到服务器消息时,就代表握手过程即将完成,下面来看
public void messageReceived(NextFilter nextFilter, IoSession session, Object obj) throws Exception {
...
switch (connectionState) {
case RTMP.STATE_CONNECTED:
...
case RTMP.STATE_CONNECT:
...
case RTMP.STATE_HANDSHAKE:
handshake = (OutboundHandshake) session.getAttribute(RTMPConnection.RTMP_HANDSHAKE);
handshake.addBuffer(message);
int s2Size = handshake.getBufferSize();
if (s2Size >= Constants.HANDSHAKE_SIZE) {
IoBuffer buf = handshake.getBufferAsIoBuffer();
byte[] dst = new byte[Constants.HANDSHAKE_SIZE];
buf.get(dst);
int index = buf.indexOf(handshake.getHandshakeType());
if (index != -1) {
buf.position(index);
}
if (handshake.decodeServerResponse2(IoBuffer.wrap(dst))) {
} else {
}
completeConnection(session, conn, rtmp, handshake);
}
break;
case RTMP.STATE_ERROR:
case RTMP.STATE_DISCONNECTING:
case RTMP.STATE_DISCONNECTED:
break;
default:
throw new IllegalStateException("Invalid RTMP state: " + connectionState);
}
}
STATE_HANDSHAKE状态的处理过程和第一种状态STATE_CONNECT类似,主要来看completeConnection函数,当握手完成时便会调用该函数,
private void completeConnection(IoSession session, RTMPMinaConnection conn, RTMP rtmp, OutboundHandshake handshake) {
if (handshake.useEncryption()) {
rtmp.setEncrypted(true);
session.setAttribute(RTMPConnection.RTMPE_CIPHER_IN, handshake.getCipherIn());
session.setAttribute(RTMPConnection.RTMPE_CIPHER_OUT, handshake.getCipherOut());
}
conn.getState().setState(RTMP.STATE_CONNECTED);
session.removeAttribute(RTMPConnection.RTMP_HANDSHAKE);
session.getFilterChain().addAfter("rtmpeFilter", "protocolFilter", new ProtocolCodecFilter(new RTMPMinaCodecFactory()));
BaseRTMPClientHandler handler = (BaseRTMPClientHandler) session.getAttribute(RTMPConnection.RTMP_HANDLER);
handler.connectionOpened(conn);
}
completeConnection函数主要做了四件事情,第一是将RTMPMinaConnection的状态设置为STATE_CONNECTED,表示已经与服务器建立了RTMP连接;第二是从session中移除前面握手过程使用的OutboundHandshake;第三是添加mina框架自带的ProtocolCodecFilter,该过滤器的源码在博主其他博文mina源码分析中有分析到;最后就是获得BaseRTMPClientHandler并调用其connectionOpened函数。
BaseRTMPClientHandler是在《red5源码分析—1》中RTMPClient的构造函数中设置的,其connectionOpened如下
public void connectionOpened(RTMPConnection conn) {
Channel channel = conn.getChannel((byte) 3);
PendingCall pendingCall = new PendingCall("connect");
pendingCall.setArguments(connectArguments);
Invoke invoke = new Invoke(pendingCall);
invoke.setConnectionParams(connectionParams);
invoke.setTransactionId(1);
if (connectCallback != null) {
pendingCall.registerCallback(connectCallback);
}
conn.registerPendingCall(invoke.getTransactionId(), pendingCall);
channel.write(invoke);
}
connectionOpened函数首先从RTMPConnection中获取Channel,getChannel定义如下,
public Channel getChannel(int channelId) {
Channel channel = channels.putIfAbsent(channelId, new Channel(this, channelId));
if (channel == null) {
channel = channels.get(channelId);
}
return channel;
}
因此这里就会创建一个Channel并添加进channels中。
回到connectionOpened函数中,接下来就需要向服务器发送命令”connect”,创建PendingCall、Invoke,注册回调函数connectCallback,最后调用Channel的write函数发送该命令,
public void write(IRTMPEvent event) {
if (!connection.isClosed()) {
final IClientStream stream = connection.getStreamByChannelId(id);
if (id > 3 && stream == null) {
}
final Number streamId = (stream == null) ? 0 : stream.getStreamId();
write(event, streamId);
} else {
}
}
由于这里stream还未创建,因此继续调用write函数,传入的streamId为0,
private void write(IRTMPEvent event, Number streamId) {
final Header header = new Header();
final Packet packet = new Packet(header, event);
header.setChannelId(id);
int ts = event.getTimestamp();
if (ts != 0) {
header.setTimer(event.getTimestamp());
}
header.setStreamId(streamId);
header.setDataType(event.getDataType());
connection.write(packet);
}
注意这里的id为前面的3,streamId为0,getDataType返回TYPE_INVOKE,然后通过mina框架发送该命令,后面的代码和mina框架有关,这里就不往下看了。
三. 状态STATE_CONNECTED
当客户端进入STATE_CONNECTED状态,就表示已经与服务器建立了RTMP连接了,
public void messageReceived(NextFilter nextFilter, IoSession session, Object obj) throws Exception {
...
switch (connectionState) {
case RTMP.STATE_CONNECTED:
if (rtmp.isEncrypted()) {
Cipher cipher = (Cipher) session.getAttribute(RTMPConnection.RTMPE_CIPHER_IN);
if (cipher != null) {
byte[] encrypted = new byte[message.remaining()];
message.get(encrypted);
message.clear();
message.free();
byte[] plain = cipher.update(encrypted);
IoBuffer messageDecrypted = IoBuffer.wrap(plain);
if (log.isDebugEnabled()) {
}
nextFilter.messageReceived(session, messageDecrypted);
} else {
}
} else {
nextFilter.messageReceived(session, obj);
}
break;
case RTMP.STATE_CONNECT:
...
case RTMP.STATE_HANDSHAKE:
...
case RTMP.STATE_ERROR:
case RTMP.STATE_DISCONNECTING:
case RTMP.STATE_DISCONNECTED:
break;
default:
}
}
在STATE_CONNECTED状态下,接收到的消息有两种选择,第一种要进行加密的处理,这里就不仔细看了,如果不需要加密的处理,就直接调用nextFilter的messageReceived函数,这属于nextFilter在mina框架中最后就会调用RTMPMinaIoHandler的messageReceived函数。
总结一下,客户端在与服务器的握手过程中会经历从STATE_CONNECT到STATE_HANDSHAKE再到STATE_CONNECTED三个状态。在握手成功后,会向服务器发送”connect”命令进行第二次连接。《red5源码分析—5》会详细分析服务器如何处理”connect”命令。
下一章将分析服务器端的握手过程。