red5源码分析---6

来源:互联网 发布:阴阳师辅助软件ios 编辑:程序博客网 时间:2024/05/21 23:54

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

服务器端代码

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

0 0