red5源码分析---6

red5源码分析—客户端和服务器的命令处理

在《red5源码分析—5》中可以知道,在RTMP握手完毕后,客户端会向服务器发送connect命令,connect命令的主要作用就是要和red5服务器上的某个Scope相连接,连接完成后,会向客户端发送带宽协调的指令,ping指令,和一个带宽检测指令。下面先分析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时一样,所以往下就不分析了。

BandWidth命令

服务器端代码

当客户端发送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函数继续处理,但这两个函数在服务器端是空函数,也即服务器什么也不做了。

checkBandWidth命令

服务器端代码

同样,在服务器端处理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函数为空,处理函数只是原数据返回,所以这里不看。

服务器端代码

服务器端对应的代码也为空,因此也是留给开发人员去添加函数进行处理了。

你可能感兴趣的:(red5源码分析---6)