在《red5源码分析—5》中可以知道,在RTMP握手完毕后,客户端会向服务器发送connect命令,connect命令的主要作用就是要和red5服务器上的某个Scope相连接,连接完成后,会向客户端发送带宽协调的指令,ping指令,和一个带宽检测指令。下面先分析ping指令。
这里先贴一下在服务器将客户端和某个Scope相连后发出的ping指令代码,
...
conn.ping(new Ping(Ping.STREAM_BEGIN, 0, -1));
...
Ping的构造函数很简单,就是一些基本的赋值,下面来看conn的ping函数,
public void ping(Ping ping) {
getChannel(2).write(ping);
}
因此这里就是简单的发送给客户端了。
客户端接收到服务器发来的ping消息后,根据前面几章的分析,最后会调用到BaseRTMPClientHandler的onPing函数,下面来看,
protected void onPing(RTMPConnection conn, Channel channel, Header source, Ping ping) {
switch (ping.getEventType()) {
case Ping.PING_CLIENT:
case Ping.STREAM_BEGIN:
case Ping.RECORDED_STREAM:
case Ping.STREAM_PLAYBUFFER_CLEAR:
Ping pong = new Ping();
pong.setEventType(Ping.PONG_SERVER);
pong.setValue2((int) (System.currentTimeMillis() & 0xffffffff));
conn.ping(pong);
break;
case Ping.STREAM_DRY:
break;
case Ping.CLIENT_BUFFER:
...
break;
case Ping.PING_SWF_VERIFY:
...
break;
case Ping.BUFFER_EMPTY:
break;
case Ping.BUFFER_FULL:
break;
default:
}
}
因为服务器发来的ping命令的eventType是STREAM_BEGIN,因此只看前面一部分,客户端将自己当前的毫秒数发送给服务器。下面再来看服务器端的处理。
服务器收到客户端的PONG_SERVER消息后,最终会进入RTMPHandler的onPing函数,代码如下
protected void onPing(RTMPConnection conn, Channel channel, Header source, Ping ping) {
switch (ping.getEventType()) {
case Ping.CLIENT_BUFFER:
...
break;
case Ping.PONG_SERVER:
conn.pingReceived(ping);
break;
default:
}
}
这里直接调用RTMPMinaConnection的pingReceived函数,定义如下
public void pingReceived(Ping pong) {
long now = System.currentTimeMillis();
Number previousPingValue = lastPingSentOn.get() & 0xffffffff;
if (pong.getValue2() == previousPingValue) {
lastPingRoundTripTime.set((int) ((now & 0xffffffff) - pong.getValue2().intValue()));
} else {
if (getPendingMessages() > 4) {
Number pingRtt = (now & 0xffffffff) - pong.getValue2().intValue();
}
}
lastPongReceivedOn.set(now);
}
这里就是简单的赋值,记录一下本次ping的各个结果值。
既然说到了ping,这里就分析一下上一章中出现的KeepAliveTask,出现在startRoundTripMeasurement函数中,
...
keepAliveTask = scheduler.scheduleAtFixedRate(new KeepAliveTask(), pingInterval);
...
该任务用于保持长连接,下面就来看,
public void run() {
if (state.getState() == RTMP.STATE_CONNECTED) {
if (running.compareAndSet(false, true)) {
try {
if (isConnected()) {
long now = System.currentTimeMillis();
long currentReadBytes = getReadBytes();
long previousReadBytes = lastBytesRead.get();
if (currentReadBytes > previousReadBytes) {
if (lastBytesRead.compareAndSet(previousReadBytes, currentReadBytes)) {
lastBytesReadTime = now;
}
if (isIdle()) {
onInactive();
}
} else {
long lastPingTime = lastPingSentOn.get();
long lastPongTime = lastPongReceivedOn.get();
if (lastPongTime > 0 && (lastPingTime - lastPongTime > maxInactivity) && (now - lastBytesReadTime > maxInactivity)) {
onInactive();
} else {
ping();
}
}
} else {
onInactive();
}
} catch (Exception e) {
} finally {
running.compareAndSet(true, false);
}
}
}
}
首先,如果在每次KeepAliveTask启动间隙有数据读入,就通过isIdle检查ping的状态,
public boolean isIdle() {
long lastPingTime = lastPingSentOn.get();
long lastPongTime = lastPongReceivedOn.get();
boolean idle = (lastPongTime > 0 && (lastPingTime - lastPongTime > maxInactivity));
return idle;
}
lastPingTime表示最近一次ping的时间,lastPongTime表示最后一次客户端相应ping的时间,它们的差就表示客户端最后一次响应ping的时间距离最近一次ping的时间有多长,当超过这个时间时,就表示服务器发送了很多ping请求但是客户端没响应,因此返回true,继而调用onInactive关闭连接。
回到KeepAliveTask中,假设服务器在KeepAliveTask启动间隙没有数据读入,就要判断是否很久没有响应ping请求了,并且已经过了很长时间没有读入数据了,“很久”的界限就是maxInactivity,如果满足,就关闭连接,如果不满足,就向客户端发送ping命令。这里不带参的ping函数如下,
public void ping() {
long newPingTime = System.currentTimeMillis();
if (lastPingSentOn.get() == 0) {
lastPongReceivedOn.set(newPingTime);
}
Ping pingRequest = new Ping();
pingRequest.setEventType(Ping.PING_CLIENT);
lastPingSentOn.set(newPingTime);
int now = (int) (newPingTime & 0xffffffff);
pingRequest.setValue2(now);
ping(pingRequest);
}
这里设置了前面提到的lastPingSentOn,主要是事件类型改变为了PING_CLIENT,但是客户端的处理方式和前面事件类型为STREAM_BEGIN时一样,所以往下就不分析了。
当客户端发送connect命令连接服务器scope时,在连接过程中,服务器会发送两个和带宽相关的指令回给客户端,这段代码如下,
two.write(new ServerBW(defaultServerBandwidth));
two.write(new ClientBW(defaultClientBandwidth, (byte) limitType));
因此发送了ServerBW和ClientBW两个类,它们对应的dataType分别是TYPE_SERVER_BANDWIDTH和TYPE_CLIENT_BANDWIDTH。
根据上面说的dataType,当带宽的信息到达客户端后,会分别调用onServerBandwidth和onClientBandwidth进行处理,
protected void onServerBandwidth(RTMPConnection conn, Channel channel, ServerBW message) {
int bandwidth = message.getBandwidth();
if (bandwidth != bytesReadWindow) {
ClientBW clientBw = new ClientBW(bandwidth, (byte) 2);
channel.write(clientBw);
}
}
protected void onClientBandwidth(RTMPConnection conn, Channel channel, ClientBW message) {
int bandwidth = message.getBandwidth();
if (bandwidth != bytesWrittenWindow) {
ServerBW serverBw = new ServerBW(bandwidth);
channel.write(serverBw);
}
}
无论是发还是收的带宽,这里就是和本地的Window相比,如果不相等就发送给服务器。实际情况可以修改这部分代码以调整客户端带宽。
服务器端接收到客户端反馈的信息后,也会调用本地的onServerBandwidth和onClientBandwidth函数继续处理,但这两个函数在服务器端是空函数,也即服务器什么也不做了。
同样,在服务器端处理connect命令时,会发送checkBandWidth的命令,代码如下,
public void checkBandwidth() {
ServerClientDetection detection = new ServerClientDetection();
detection.checkBandwidth(Red5.getConnectionLocal());
}
ServerClientDetection的构造函数为空,因此直接看checkBandwidth函数,
public void checkBandwidth(IConnection conn) {
calculateClientBw(conn);
}
public void calculateClientBw(IConnection conn) {
this.conn = conn;
Random rnd = new Random();
rnd.nextBytes(payload);
rnd.nextBytes(payload1);
startBytesWritten = conn.getWrittenBytes();
startTime = System.nanoTime();
callBWCheck("");
}
payload和payload1是两个随机数组,startBytesWritten记录该连接发出的字节数,startTime是当前纳秒数,最关键的是最后调用的callBWCheck,
private void callBWCheck(Object payload) {
IConnection conn = Red5.getConnectionLocal();
Map<String, Object> statsValues = new HashMap<String, Object>();
statsValues.put("count", packetsReceived.get());
statsValues.put("sent", packetsSent.get());
statsValues.put("timePassed", timePassed);
statsValues.put("latency", latency);
statsValues.put("cumLatency", cumLatency);
statsValues.put("payload", payload);
if (conn instanceof IServiceCapableConnection) {
packetsSent.incrementAndGet();
((IServiceCapableConnection) conn).invoke("onBWCheck", new Object[] { statsValues }, this);
}
}
这里进行相应的设置后就调用RTMPMinaConnection的invoke函数发送请求了,该函数定义如下,
public void invoke(String method, Object[] params, IPendingServiceCallback callback) { IPendingServiceCall call = new PendingCall(method, params); if (callback != null) { call.registerCallback(callback); } invoke(call);
}
因此,这里注册了回调函数,然后就向客户端发送该invoke请求了。
客户端对应的onBWCheck函数为空,处理函数只是原数据返回,所以这里不看。
服务器端对应的代码也为空,因此也是留给开发人员去添加函数进行处理了。