ZLMediaKit服务端RTMP握手过程

6 篇文章 1 订阅
4 篇文章 0 订阅

RTMP握手过程

RTMP协议握手过程中,服务端和客户端都会发送三个包,客户端是C0、C1、C2,服务端是S0、S1、S2
握手的顺序如下:
1、客户端开始发送C0,C1
2、客户端必须收到S1后,才发送C2
3、客户端必须收到S2后才开始发送其他信息(控制信息和音视频数据)
4、服务器要等收到C0才能发送S0和S1
5、服务器必须等C1后才能发送S2 
6、服务器必须等收到C2之后才能发送其他数据(控制信息和音视频数据)

简化如下

  • client–> server : 发送一个创建流的请求 (C0、C1)

  • server–> client : 返回一个流的索引号 (S0、S1、S2)

  • client–> server : 开始发送 (C2)

  • client–> server : 发送音视频数据

    其中 C0 和 S0 的格式如下
     C0 和 S0 的格式
    C1 C2 和 S1 S2的格式如下
     C1 C2 和 S1 S2 的格式
    数据规律:C1 = S2、C2 = S1

ZLMediaKit中RTMP握手过程

最初在此函数中接收数据

void RtmpSession::onRecv(const Buffer::Ptr &buf) {
    _ticker.resetTime();
    try {
        _total_bytes += buf->size();
        onParseRtmp(buf->data(), buf->size());
    } catch (exception &ex) {
        shutdown(SockException(Err_shutdown, ex.what()));
    }
}

将所有接收的数据保存在_remain_data 然后通过 onSearchPacketTail 来处理数据(根据数据类型的不同,有不同的处理方式 比如 handle_C0C1handle_C2

void HttpRequestSplitter::input(const char *data,size_t len) {
    const char *ptr = data;
    if(!_remain_data.empty()){
        _remain_data.append(data,len);
        data = ptr = _remain_data.data();
        len = _remain_data.size();
    }

    splitPacket:

    /*确保ptr最后一个字节是0,防止strstr越界
     *由于ZLToolKit确保内存最后一个字节是保留未使用字节并置0,
     *所以此处可以不用再次置0
     *但是上层数据可能来自其他渠道,保险起见还是置0
     */

    char &tail_ref = ((char *) ptr)[len];
    char tail_tmp = tail_ref;
    tail_ref = 0;

    //数据按照请求头处理
    const char *index = nullptr;
    _remain_data_size = len;
    while (_content_len == 0 && _remain_data_size > 0 && (index = onSearchPacketTail(ptr,_remain_data_size)) != nullptr) {
        if (index == ptr) {
            break;
        }
        if (index < ptr || index > ptr + _remain_data_size) {
            throw std::out_of_range("上层分包逻辑异常");
        }
        //_content_len == 0,这是请求头
        const char *header_ptr = ptr;
        ssize_t header_size = index - ptr;
        ptr = index;
        _remain_data_size = len - (ptr - data);
        _content_len = onRecvHeader(header_ptr, header_size);
    }

    if(_remain_data_size <= 0){
        //没有剩余数据,清空缓存
        _remain_data.clear();
        return;
    }

    /*
     * 恢复末尾字节
     * 移动到这来,目的是防止HttpRequestSplitter::reset()导致内存失效
     */
    tail_ref = tail_tmp;

    if(_content_len == 0){
        //尚未找到http头,缓存定位到剩余数据部分
        _remain_data.assign(ptr,_remain_data_size);
        return;
    }

    //已经找到http头了
    if(_content_len > 0){
        //数据按照固定长度content处理
        if(_remain_data_size < (size_t)_content_len){
            //数据不够,缓存定位到剩余数据部分
            _remain_data.assign(ptr, _remain_data_size);
            return;
        }
        //收到content数据,并且接受content完毕
        onRecvContent(ptr,_content_len);

        _remain_data_size -= _content_len;
        ptr += _content_len;
        //content处理完毕,后面数据当做请求头处理
        _content_len = 0;

        if(_remain_data_size > 0){
            //还有数据没有处理完毕
            _remain_data.assign(ptr,_remain_data_size);
            data = ptr = (char *)_remain_data.data();
            len = _remain_data.size();
            goto splitPacket;
        }
        _remain_data.clear();
        return;
    }


    //_content_len < 0;数据按照不固定长度content处理
    onRecvContent(ptr,_remain_data_size);//消费掉所有剩余数据
    _remain_data.clear();
}

经过下述函数中的next_step_func进入函数handle_C0C1 处理客户端发送过来的C0 C1

const char *RtmpProtocol::onSearchPacketTail(const char *data,size_t len){
    //移动拷贝提高性能
    auto next_step_func(std::move(_next_step_func));
    //执行下一步
    auto ret = next_step_func(data, len);
    if (!_next_step_func) {
        //为设置下一步,恢复之
        next_step_func.swap(_next_step_func);
    }
    return ret;
}
for server 
const char * RtmpProtocol::handle_C0C1(const char *data, size_t len) {
    if (len < 1 + C1_HANDSHARK_SIZE) {
        //need more data!
        return nullptr;
    }
    if (data[0] != HANDSHAKE_PLAINTEXT) {
        throw std::runtime_error("only plaintext[0x03] handshake supported");
    }
    // \x00 == 0x00  表示两位16进制
    if (memcmp(data + 5, "\x00\x00\x00\x00", 4) == 0) {
        //simple handsharke
        handle_C1_simple(data);
    } else {
#ifdef ENABLE_OPENSSL
        //complex handsharke
        handle_C1_complex(data);
#else
        WarnL << "未打开ENABLE_OPENSSL宏,复杂握手采用简单方式处理,flash播放器可能无法播放!";
        handle_C1_simple(data);
#endif//ENABLE_OPENSSL
    }
    return data + 1 + C1_HANDSHARK_SIZE;
}

上述函数检查如果数据长度、C0 和C1中的版本号正确就进入函数 handle_C1_simple 中处理,即回复S0 S1 S2 并将下一步的处理函数设置为 handle_C2

void RtmpProtocol::handle_C1_simple(const char *data){
    //发送S0
    char handshake_head = HANDSHAKE_PLAINTEXT;
    onSendRawData(obtainBuffer(&handshake_head, 1));
    //发送S1
    RtmpHandshake s1(0);
    onSendRawData(obtainBuffer((char *) &s1, C1_HANDSHARK_SIZE));
    //发送S2
    onSendRawData(obtainBuffer(data + 1, C1_HANDSHARK_SIZE));
    //等待C2
    _next_step_func = [this](const char *data, size_t len) {
        //握手结束并且开始进入解析命令模式
        return handle_C2(data, len);
    };
}

后面来的数据就会进入函数handle_C2处理,至此握手结束,最后会将数据的处理函数设置为handle_rtmp,用来处理后续的消息和数据(例如控制消息和音视频帧)

const char* RtmpProtocol::handle_C2(const char *data, size_t len) {
    if (len < C1_HANDSHARK_SIZE) {
        //need more data!
        return nullptr;
    }
    _next_step_func = [this](const char *data, size_t len) {
        return handle_rtmp(data, len);
    };

    //握手结束,进入命令模式
    return handle_rtmp(data + C1_HANDSHARK_SIZE, len - C1_HANDSHARK_SIZE);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值