一个简单的RTMP服务器实现 --- RTMP复杂握手(Complex Handshake)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011728480/article/details/86131115

#PS:要转载请注明出处,本人版权所有

#PS:这个只是 《 我自己 》理解,如果和你的

#原则相冲突,请谅解,勿喷

背景

参考前置文章:《一个简单的RTMP服务器实现 — RTMP与H264》
https://blog.csdn.net/u011728480/article/details/85770696

前置知识

《一个简单的RTMP服务器实现 — RTMP与H264》:https://blog.csdn.net/u011728480/article/details/85770696
《一个简单的RTMP服务器实现 — RTMP与FLV》:https://blog.csdn.net/u011728480/article/details/85780974
《一个简单的RTMP服务器实现 — RTMP实现要点》:https://blog.csdn.net/u011728480/article/details/86010851

为啥需要RTMP复杂握手

曾经的我,那么的天真,我按照官方文档以及官方文档的相关资料进行了RTMP的服务器的实现。奈何,前端使用的是Video.js,需要调用flash进行rtmp流播放,但是就是播放不起来。

在许多次尝试后,flash还是不能够播放我传输的rtmp流。我就开始了怀疑人生了。因为vlc和plotplayer都是能播放的,但是flash不能够播放。于是乎,我就猜想是flash播放器有什么特殊的毛病,需要我去治一治。

又过了许久,我搜遍了网络,我第一次看见这个问题的可能答案是在某论坛的一篇过期的帖子里面。

原因是flash播放器需要特殊的握手方式才能够播放h264+aac的rtmp流(具体表现为:播放器连接上了rtmp服务器,但是没有任何图像和声音)。于是我知道了,我需要改变自己的服务器的握手流程。

经过前期在RTMP中的折腾,我知道我要找的东西是RTMP复杂握手流程。于是就去网上找了找。经过查找,发现了一篇不错的文章:https://blog.csdn.net/win_lin/article/details/13006803 (这篇文章貌似是srs的作者写的,里面就有我现在这个问题的答案,这里及其感谢大佬的文章。)

本文内容是上文提到的大佬的文章的一个补充,希望大家结合着一起看,实现自己的RTMP服务器。

RTMP复杂握手

下图为S1,C1。S2,C2的结构图
在这里插入图片描述

上图中一眼就可以看懂相关包的结构。其中需要注意的是一个c1_s1-joined的结构。他是把s1,c1中的digest的32bytes去除后,然后把剩下的内容拼接在一起形成的一个数组。

下面这两个数组是一个常量,在后面的计算会用到。

    private static final byte[] FP_KEY = {
        (byte) 0x47, (byte) 0x65, (byte) 0x6E, (byte) 0x75, (byte) 0x69, (byte) 0x6E, (byte) 0x65, (byte) 0x20,
        (byte) 0x41, (byte) 0x64, (byte) 0x6F, (byte) 0x62, (byte) 0x65, (byte) 0x20, (byte) 0x46, (byte) 0x6C,
        (byte) 0x61, (byte) 0x73, (byte) 0x68, (byte) 0x20, (byte) 0x50, (byte) 0x6C, (byte) 0x61, (byte) 0x79,
        (byte) 0x65, (byte) 0x72, (byte) 0x20, (byte) 0x30, (byte) 0x30, (byte) 0x31, // Genuine Adobe Flash Player 001
        (byte) 0xF0, (byte) 0xEE, (byte) 0xC2, (byte) 0x4A, (byte) 0x80, (byte) 0x68, (byte) 0xBE, (byte) 0xE8,
        (byte) 0x2E, (byte) 0x00, (byte) 0xD0, (byte) 0xD1, (byte) 0x02, (byte) 0x9E, (byte) 0x7E, (byte) 0x57,
        (byte) 0x6E, (byte) 0xEC, (byte) 0x5D, (byte) 0x2D, (byte) 0x29, (byte) 0x80, (byte) 0x6F, (byte) 0xAB,
        (byte) 0x93, (byte) 0xB8, (byte) 0xE6, (byte) 0x36, (byte) 0xCF, (byte) 0xEB, (byte) 0x31, (byte) 0xAE};


    private static final byte FMSKey[] = {
            (byte)0x47, (byte)0x65, (byte)0x6e, (byte)0x75, (byte)0x69, (byte)0x6e, (byte)0x65, (byte)0x20,
            (byte)0x41, (byte)0x64, (byte)0x6f, (byte)0x62, (byte)0x65, (byte)0x20, (byte)0x46, (byte)0x6c,
            (byte)0x61, (byte)0x73, (byte)0x68, (byte)0x20, (byte)0x4d, (byte)0x65, (byte)0x64, (byte)0x69,
            (byte)0x61, (byte)0x20, (byte)0x53, (byte)0x65, (byte)0x72, (byte)0x76, (byte)0x65, (byte)0x72,
            (byte)0x20, (byte)0x30, (byte)0x30, (byte)0x31, // Genuine Adobe Flash Media Server 001
            (byte)0xf0, (byte)0xee, (byte)0xc2, (byte)0x4a, (byte)0x80, (byte)0x68, (byte)0xbe, (byte)0xe8,
            (byte)0x2e, (byte)0x00, (byte)0xd0, (byte)0xd1, (byte)0x02, (byte)0x9e, (byte)0x7e, (byte)0x57,
            (byte)0x6e, (byte)0xec, (byte)0x5d, (byte)0x2d, (byte)0x29, (byte)0x80, (byte)0x6f, (byte)0xab,
            (byte)0x93, (byte)0xb8, (byte)0xe6, (byte)0x36, (byte)0xcf, (byte)0xeb, (byte)0x31, (byte)0xae
    }; 

C1,S1 伪代码实现

下面会列出c1的伪代码实现

unsigned char * const C1 = new unsigned char[1536];//s1,c1,s2,c2大小都是1536字节

Random(C1);//把C1每个字节随机化

C1[0-3] = current_time();

C1[4-7] = {0x80, 0x00, 0x07, 0x02};//这个是定值

//自行决定使用哪种schema结构,定义c1_schema_type


int key_offset = Random(0-632);//在取值范围[0-632) (764-4-128,观察key结构可知)中随机一个值赋值给key_offset(key-data的相对位置).

int digest_offset = Random(0-728);//在取值范围[0-728)(764-32-4,观察digest结构可知)中随机一个值赋值给digest_offset(digest-data的相对位置).

int absolute_key_offset = CalAbsoluteKeyOffset(key_offset, c1_schema_type );//根据c1_schema_type,按照相同类型获取key结构中,key-data在数组中的绝对位置

int absolute_digest_offset = CalAbsoluteDigestOffset(digest_offset , c1_schema_type );//根据c1_schema_type,按照相同类型获取digest结构中,digest-data在数组中的绝对位置

SetC1KeyOffset(key_offset, c1_schema_type);//把keyoffset设置到C1中,注意,key结构中这里的offset 的4个字节的值必须加起来等于key_offset的值。

SetC1DigestOffset(digest_offset , c1_schema_type);//把digest_offset 设置到C1中,注意,digest结构中这里的offset 的4个字节的值必须加起来等于digest_offset 的值。

c1_s1_joined_array = GetC1JoinedArray(absolute_digest_offset);//这里是根据绝对digest偏移,然后去掉digest32个字节,然后返回digest的前后组合得到的数组结果(1536-32)

c1_digest = HMACsha256(c1_s1_joined_array , FP_KEY, 30);//调用openssl中的Hmacsha256算法计算秘钥
SetC1Digest(c1_digest , c1_schema_type);//把计算出来的digest设置到c1中去。然后c1构造完成,即可发送给服务器

注意:C1中的key值就是我们的随机值即可。严格按照顺序计算,设置时间,版本,key_offset,digest_offset,然后才能够计算c1_s1_joined_array,最后得到c1_digest。

下面会列出s1的伪代码实现

unsigned char * const S1 = new unsigned char[1536];//s1,c1,s2,c2大小都是1536字节

Random(S1);//把S1每个字节随机化

S1[0-3] = current_time();

S1[4-7] = {0x04, 0x05, 0x00, 0x01};//这个是定值版本。

c1_schema_type = GetC1SchemaType();//获取C1的schema type。得出是key-digest structure 还是 digest-key structure

int key_offset = Random(0-632);//在取值范围[0-632) (764-4-128,观察key结构可知)中随机一个值赋值给key_offset(key-data的相对位置).

int digest_offset = Random(0-728);//在取值范围[0-728)(764-32-4,观察digest结构可知)中随机一个值赋值给digest_offset(digest-data的相对位置).

int absolute_key_offset = CalAbsoluteKeyOffset(key_offset, c1_schema_type );//根据c1_schema_type,按照相同类型获取key结构中,key-data在数组中的绝对位置

int absolute_digest_offset = CalAbsoluteDigestOffset(digest_offset , c1_schema_type );//根据c1_schema_type,按照相同类型获取digest结构中,digest-data在数组中的绝对位置

SetS1KeyOffset(key_offset, c1_schema_type);//把keyoffset设置到S1中,注意,key结构中这里的offset 的4个字节的值必须加起来等于key_offset的值。

SetS1DigestOffset(digest_offset , c1_schema_type);//把digest_offset 设置到S1中,注意,digest结构中这里的offset 的4个字节的值必须加起来等于digest_offset 的值。

unsigned char * const c1_key = GetC1Key();//获取C1的key

//这里参考srs开源框架的实现,调用openssl的DH算法中相关内容。
//根据上文那篇srs作者的文章的评论部分内容,这里相当于是要算出一个128公钥作为s1的key就可以了。不需要得到共享秘钥。
unsigned char * const s1_key = GetDHPublicKey();

SetS1Key(s1_key , c1_schema_type);//设置s1 key 到s1数组中,方便计算c1_s1-joined结构。
c1_s1_joined_array = GetS1JoinedArray(absolute_digest_offset);//这里是根据绝对digest偏移,然后去掉digest32个字节,然后返回digest的前后组合得到的数组结果(1536-32)

s1_digest = HMACsha256(c1_s1_joined_array , FMSKey, 36);//调用openssl中的Hmacsha256算法计算秘钥

SetS1Digest(s1_digest , c1_schema_type);//把计算出来的digest设置到s1中去。然后s1构造完成,即可发送给客户端

这里必须严格按照流程走:
把1536字节buffer随机化,设置时间,版本信息。设置s1_key,s1_key_offset信息,然后设置s1_digest_offset信息。以上都做完以后,计算出c1_s1_joined_array 。根据c1_s1_joined_array ,FMSKey计算出s1_digest,把s1_digest设置到s1buffer中。到了这一步,即可把s1发送出去了。

C2 S2的伪代码实现

C2伪代码实现

unsigned char * const C2 = new unsigned char[1536];//s1,c1,s2,c2大小都是1536字节

Random(C2);//把C2每个字节随机化

s1_digest = GetS1Digest();//得到s1的digest

tmp_key = HMACsha256(s1_digest, FPKey, 62);

c2_random_data = GetC2RandomData();//得到c2[0~1503]

c2_digest = HMACsha256(c2_random_data, tmp_key, 32);

SetC2Digest(c2_digest);

就是连续计算秘钥得到C2 key即可

S2伪代码实现

unsigned char * const S2 = new unsigned char[1536];//s1,c1,s2,c2大小都是1536字节

Random(S2);//把S2每个字节随机化

c1_digest = GetC1Digest();//得到c1的digest

tmp_key = HMACsha256(c1_digest, FMSKey, 68);

s2_random_data = GetS2RandomData();//得到s2[0~1503]

s2_digest = HMACsha256(s2_random_data, tmp_key, 32);

SetS2Digest(s2_digest);

总结:
1 就是按照别人推断出来的东西进行代码实现,同时明确一下一些字段定义。
2 按照大佬文中评论,验证了一下,public_key和share_key对RTMP没有影响。

本系列文末总结

1 按照一个文档,实现了相关功能是一件有成就感的事情。
2 现在已经是2019年了,你要相信,你要做的事情,极有可能是别人做过的,可以去多查查资料以备参考。
3 静下心来做事,我发现有些时候我很浮躁。同时敢怀疑别人,并且思索怀疑的理由。

#PS:请尊重原创,不喜勿喷

#PS:要转载请注明出处,本人版权所有.

有问题请留言,看到后我会第一时间回复

猜你喜欢

转载自blog.csdn.net/u011728480/article/details/86131115