美文网首页
RTMP网络连接分析

RTMP网络连接分析

作者: 狼爽过羊 | 来源:发表于2018-10-26 19:36 被阅读33次

    RTMP_Connect

    librtmp用RTMP_Connect这个方法建立网络连接,网络连接代表了服务端应用程序和客户端之间基本的连通关系。 在RTMP_ParseURL方法中, 可以通过传入的url解出来hostportapplication
    用解出来的host和port建立socket,然后做一些rtmp握手之类的工作。

    int
    RTMP_Connect(RTMP *r, RTMPPacket *cp)
    {
        struct sockaddr_in service;
        if (!r->Link.hostname.av_len)
            return FALSE;
    
        memset(&service, 0, sizeof(struct sockaddr_in));
        service.sin_family = AF_INET;
        RTMP_Log(RTMP_LOGDEBUG, "Link.sockport = %d", r->Link.sockshost);
        if (r->Link.socksport) {
            /* Connect via SOCKS */
            if (!add_addr_info(&service, &r->Link.sockshost, r->Link.socksport))
                return FALSE;
        } else {
            RTMP_Log(RTMP_LOGDEBUG, "hostname: %s, port = %d\n", r->Link.hostname.av_val, r->Link.port);
            /* Connect directly */
            if (!add_addr_info(&service, &r->Link.hostname, r->Link.port))
                return FALSE;
        }
    
        // 创建socket, 设置一些参数, 接收数据超时时间,禁用Nagle算法 
        if (!RTMP_Connect0(r, (struct sockaddr *)&service))
            return FALSE;
    
        r->m_bSendCounter = TRUE;
    
        return RTMP_Connect1(r, cp);
    }
    

    RTMP_Connect0

    RTMP_Connect0方法中主要就是创建tcp socket, 然后连接服务器, 设置超时, 禁用Nagle算法

    int
    RTMP_Connect0(RTMP *r, struct sockaddr * service)
    {
        int on = 1;
        r->m_sb.sb_timedout = FALSE;
        r->m_pausing = 0;
        r->m_fDuration = 0.0;
    
        r->m_sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (r->m_sb.sb_socket != -1) {
            if (connect(r->m_sb.sb_socket, service, sizeof(struct sockaddr)) < 0) {
                int err = GetSockError();
                RTMP_Log(RTMP_LOGERROR, "%s, failed to connect socket. %d (%s)",
                __FUNCTION__, err, strerror(err));
                RTMP_Close(r);
                return FALSE;
            }
    
            if (r->Link.socksport) {
                RTMP_Log(RTMP_LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__);
                if (!SocksNegotiate(r)) {
                    RTMP_Log(RTMP_LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__);
                    RTMP_Close(r);
                    return FALSE;
                }
            }
        } else {
            RTMP_Log(RTMP_LOGERROR, "%s, failed to create socket. Error: %d", __FUNCTION__,
            GetSockError());
            return FALSE;
        }
    
        /* set timeout */
        {
            SET_RCVTIMEO(tv, r->Link.timeout);
            if (setsockopt
            (r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv))) {
                RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!",
                __FUNCTION__, r->Link.timeout);
            }
        }
        // 禁用Nagle算法, 保证了数据实时性
        setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on));
    
        // @remark debug info by http://github.com/ossrs/srs
        _srs_state = 2;
    
        return TRUE;
    }
    

    RTMP_Connect1

    RTMP_Connect1主要包括握手(HandShake),还有向服务器发送连接命令(SendConnectPacket)

    int
    RTMP_Connect1(RTMP *r, RTMPPacket *cp)
    {
        if (r->Link.protocol & RTMP_FEATURE_SSL) {
    #if defined(CRYPTO) && !defined(NO_SSL)
            TLS_client(RTMP_TLS_ctx, r->m_sb.sb_ssl);
            TLS_setfd(r->m_sb.sb_ssl, r->m_sb.sb_socket);
            if (TLS_connect(r->m_sb.sb_ssl) < 0) {
                RTMP_Log(RTMP_LOGERROR, "%s, TLS_Connect failed", __FUNCTION__);
                RTMP_Close(r);
                return FALSE;
            }
    #else
            RTMP_Log(RTMP_LOGERROR, "%s, no SSL/TLS support", __FUNCTION__);
            RTMP_Close(r);
            return FALSE;
    
    #endif
        }
        RTMP_Log(RTMP_LOGDEBUG, "Link.protocal: %d", r->Link.protocol);
        if (r->Link.protocol & RTMP_FEATURE_HTTP) {
            r->m_msgCounter = 1;
            r->m_clientID.av_val = NULL;
            r->m_clientID.av_len = 0;
            HTTP_Post(r, RTMPT_OPEN, "", 1);
            if (HTTP_read(r, 1) != 0) {
                r->m_msgCounter = 0;
                RTMP_Log(RTMP_LOGDEBUG, "%s, Could not connect for handshake", __FUNCTION__);
                RTMP_Close(r);
                return 0;
            }
            r->m_msgCounter = 0;
        }
        RTMP_Log(RTMP_LOGDEBUG, "%s, ... connected, handshaking", __FUNCTION__);
        if (!HandShake(r, TRUE)) {
            RTMP_Log(RTMP_LOGERROR, "%s, handshake failed.", __FUNCTION__);
            RTMP_Close(r);
            return FALSE;
        }
        RTMP_Log(RTMP_LOGDEBUG, "%s, handshaked", __FUNCTION__);
    
        if (!SendConnectPacket(r, cp)) {
            RTMP_Log(RTMP_LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
            RTMP_Close(r);
            return FALSE;
        }
        return TRUE;
    }
    

    HandShake

    一个RTMP连接以握手开始, RTMP的握手不同以其他协议:RTMP握手由三个固定长度的块组成,而不像其他协议一样带有报头的可变长度的块。客户端和服务端各自发送三块数据,

    • 客户端:C0、C1、C2
    • 服务端:S0、S1、S2

    下面时librtmp中对于握手的处理

    static int
    HandShake(RTMP *r, int FP9HandShake)
    {
        int i;
        uint32_t uptime, suptime;
        int bMatch;
        char type;
        char clientbuf[RTMP_SIG_SIZE + 1], *clientsig = clientbuf + 1;
        char serversig[RTMP_SIG_SIZE];
    
        clientbuf[0] = 0x03;        /* not encrypted */
    
        uptime = htonl(RTMP_GetTime());
        memcpy(clientsig, &uptime, 4);
    
        memset(&clientsig[4], 0, 4);
    
    #ifdef _DEBUG
        for (i = 8; i < RTMP_SIG_SIZE; i++)
            clientsig[i] = 0xff;
    #else
        for (i = 8; i < RTMP_SIG_SIZE; i++)
            clientsig[i] = (char)(rand() % 256);
    #endif
        // 发送C0、C1数据块
        if (!WriteN(r, clientbuf, RTMP_SIG_SIZE + 1))
            return FALSE;
    
        /* 接收S0块 */
        if (ReadN(r, &type, 1) != 1)    /* 0x03 or 0x06 */
            return FALSE;
    
        RTMP_Log(RTMP_LOGDEBUG, "%s: Type Answer   : %02X", __FUNCTION__, type);
    
        if (type != clientbuf[0])
            RTMP_Log(RTMP_LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d",
                     __FUNCTION__, clientbuf[0], type);
        /* 接收S1块 */
        if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
            return FALSE;
    
        /* decode server response */
    
        memcpy(&suptime, serversig, 4);
        suptime = ntohl(suptime);
    
        RTMP_Log(RTMP_LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, suptime);
        RTMP_Log(RTMP_LOGDEBUG, "%s: FMS Version   : %d.%d.%d.%d", __FUNCTION__,
                 serversig[4], serversig[5], serversig[6], serversig[7]);
    
        /* 2nd part of handshake 发送C2块*/
        if (!WriteN(r, serversig, RTMP_SIG_SIZE))
            return FALSE;
    
        /* 接收S2块 */
        if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
            return FALSE;
    
        bMatch = (memcmp(serversig, clientsig, RTMP_SIG_SIZE) == 0);
        if (!bMatch) {
            RTMP_Log(RTMP_LOGWARNING, "%s, client signature does not match!", __FUNCTION__);
        }
        return TRUE;
    }
    

    握手顺序

    握手的顺序大致可以描述如下

    • 客户端首先发送C0C1
    • 服务端接收到C0C1块之后,向客户端发送S0S1
    • 客户端相继接收数据, 直到接收到S1数据块之后, 再服务端发送C2
    • 服务端必须等待接收到C1块之后才能发送S2
    • 客户端必须等待接收到S2块才能发送其他数据
    • 服务端必须等待接收到C1块才能发送其他数据

    握手数据块格式

    下面介绍一下C0、C1、C2和S0、S1、S2数据块的格式。

    C0和S0数据块格式

    C0和S0块都是一个单一的八位字节,以一个单独的八位整形域进行处理。

     0 1 2 3 4 5 6 7 
    +-+-+-+-+-+-+-+-+
    |    version    |
    +-+-+-+-+-+-+-+-+
    
    C1和S1的格式

    C1和S1的数据包的长度为1536字节, 包括以下字段

     0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                            time(4 bytes)                      |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                            zero(4 bytes)                      |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                            random bytes                       |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                            random bytes                       |
    |                              (const)                          |
    |                                ...                            |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    • Time(四个字节):这个字段包括了一个timestamp, 用于本终端发送的所有后续块的时间起点。这个值可以时0或者一些任意值。
    • Zero(四个字节):这个字段必须时0
    • Random data(四个字节):这个字段可以包含任意值
    C2和S2的格式

    C2和S2数据包长度都是1536字节, 基本上就是S1和C1的副本, 包含以下字段:

     0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                            time(4 bytes)                      |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                            time2(4 bytes)                     |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                            random echo                        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                            random echo                        |
    |                              (const)                          |
    |                                ...                            |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    • Time(四个字节):这个字段必须包含终端在S1(给C2)或者C1(给S2)发的timestamp
    • Time2(四个字节):这个字段必须包含终端先前发出数据包(S1或者C1)timestamp
    • Random data(四个字节):这个字段必须包含终端发出的S1(给C2)或者S2(给C1)的随机数。

    SendConnectPacket

    SendConnectPacket方法的作用是客户端发送connect命令到服务器端来请求连接到一个服务器应用的实例

    connect命令结构如下:

    字段名 类型 描述
    Command Name 字符串 命令的名字,这里是connect
    Transaction ID 数字 总是设置为1
    Command Object 对象 具有名值对的命令信息对象
    Optional User Arguments 对象 任意可选对象

    任意可选对象里面是相当与是一些用户参数, appflashverswfUrl

    下面是librtmp中的连接代码

    static int
    SendConnectPacket(RTMP *r, RTMPPacket *cp)
    {
        RTMPPacket packet;
        char pbuf[4096], *pend = pbuf + sizeof(pbuf);
        char *enc;
    
        if (cp)
            return RTMP_SendPacket(r, cp, TRUE);
    
        packet.m_nChannel = 0x03;   /* control channel (invoke) */
        packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
        packet.m_packetType = RTMP_PACKET_TYPE_INVOKE;
        packet.m_nTimeStamp = 0;
        packet.m_nInfoField2 = 0;
        packet.m_hasAbsTimestamp = 0;
        packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
    
        enc = packet.m_body;
        enc = AMF_EncodeString(enc, pend, &av_connect);
        enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
        *enc++ = AMF_OBJECT;
    
        enc = AMF_EncodeNamedString(enc, pend, &av_app, &r->Link.app);
        if (!enc)
            return FALSE;
        if (r->Link.protocol & RTMP_FEATURE_WRITE) {
            enc = AMF_EncodeNamedString(enc, pend, &av_type, &av_nonprivate);
            if (!enc)
                return FALSE;
        }
        if (r->Link.flashVer.av_len) {
            enc = AMF_EncodeNamedString(enc, pend, &av_flashVer, &r->Link.flashVer);
            if (!enc)
                return FALSE;
        }
        if (r->Link.swfUrl.av_len) {
            enc = AMF_EncodeNamedString(enc, pend, &av_swfUrl, &r->Link.swfUrl);
            if (!enc)
                return FALSE;
        }
        if (r->Link.tcUrl.av_len) {
            enc = AMF_EncodeNamedString(enc, pend, &av_tcUrl, &r->Link.tcUrl);
            if (!enc)
                return FALSE;
        }
        if (!(r->Link.protocol & RTMP_FEATURE_WRITE)) {
            enc = AMF_EncodeNamedBoolean(enc, pend, &av_fpad, FALSE);
            if (!enc)
                return FALSE;
            enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 15.0);
            if (!enc)
                return FALSE;
            enc = AMF_EncodeNamedNumber(enc, pend, &av_audioCodecs, r->m_fAudioCodecs);
            if (!enc)
                return FALSE;
            enc = AMF_EncodeNamedNumber(enc, pend, &av_videoCodecs, r->m_fVideoCodecs);
            if (!enc)
                return FALSE;
            enc = AMF_EncodeNamedNumber(enc, pend, &av_videoFunction, 1.0);
            if (!enc)
                return FALSE;
            if (r->Link.pageUrl.av_len) {
                enc = AMF_EncodeNamedString(enc, pend, &av_pageUrl, &r->Link.pageUrl);
                if (!enc)
                    return FALSE;
            }
        }
        if (r->m_fEncoding != 0.0 || r->m_bSendEncoding) {
            /* AMF0, AMF3 not fully supported yet */
            enc = AMF_EncodeNamedNumber(enc, pend, &av_objectEncoding, r->m_fEncoding);
            if (!enc)
                return FALSE;
        }
        if (enc + 3 >= pend)
            return FALSE;
        *enc++ = 0;
        *enc++ = 0;         /* end of object - 0x00 0x00 0x09 */
        *enc++ = AMF_OBJECT_END;
    
        /* add auth string */
        if (r->Link.auth.av_len) {
            enc = AMF_EncodeBoolean(enc, pend, r->Link.lFlags & RTMP_LF_AUTH);
            if (!enc)
                return FALSE;
            enc = AMF_EncodeString(enc, pend, &r->Link.auth);
            if (!enc)
                return FALSE;
        }
        if (r->Link.extras.o_num) {
            int i;
            for (i = 0; i < r->Link.extras.o_num; i++) {
                enc = AMFProp_Encode(&r->Link.extras.o_props[i], enc, pend);
                if (!enc)
                    return FALSE;
            }
        }
        packet.m_nBodySize = enc - packet.m_body;
    
        return RTMP_SendPacket(r, &packet, TRUE);
    }
    

    相关文章

      网友评论

          本文标题:RTMP网络连接分析

          本文链接:https://www.haomeiwen.com/subject/rgvltqtx.html