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

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

前置说明

  本文作为本人csdn blog的主站的备份。(BlogID=078)

  本文发布于 2019-01-09 16:57:55,现用MarkDown+图床做备份更新。blog原图已丢失,使用csdn所存的图进行更新。(BlogID=078)

环境说明

  无

背景


  参考前置文章:《一个简单的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: 要转载请注明出处,本人版权所有。

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

一个简单的RTMP服务器实现 --- RTMP复杂握手(Complex Handshake)的更多相关文章

  1. 自己动手模拟开发一个简单的Web服务器

    开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此事要躬行”,于是我们自己模拟一个简单的W ...

  2. 一个简单的web服务器

    写在前面 新的一年了,新的开始,打算重新看一遍asp.net本质论这本书,再重新认识一下,查漏补缺,认认真真的过一遍. 一个简单的web服务器 首先需要引入命名空间: System.Net,关于网络编 ...

  3. [置顶] 在Ubuntu下实现一个简单的Web服务器

    要求: 实现一个简单的Web服务器,当服务器启动时要读取配置文件的路径.如果浏览器请求的文件是可执行的则称为CGI程序,服务器并不是将这个文件发给浏览器,而是在服务器端执行这个程序,将它的标准输出发给 ...

  4. Tomcat剖析(二):一个简单的Servlet服务器

    Tomcat剖析(二):一个简单的Servlet服务器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三) ...

  5. Tomcat剖析(一):一个简单的Web服务器

    Tomcat剖析(一):一个简单的Web服务器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三):连接器 ...

  6. 自己模拟的一个简单的web服务器

    首先我为大家推荐一本书:How Tomcat Works.这本书讲的很详细的,虽然实际开发中我们并不会自己去写一个tomcat,但是对于了解Tomcat是如何工作的还是很有必要的. Servlet容器 ...

  7. java实现一个简单的Web服务器

    注:本段内容来源于<JAVA 实现 简单的 HTTP服务器> 1. HTTP所有状态码 状态码 状态码英文名称 中文描述 100 Continue 继续.客户端应继续其请求 101 Swi ...

  8. 响应式编程笔记三:一个简单的HTTP服务器

    # 响应式编程笔记三:一个简单的HTTP服务器 本文我们将继续前面的学习,但将更多的注意力放在用例和编写实际能用的代码上面,而非基本的APIs学习. 我们会看到Reactive是一个有用的抽象 - 对 ...

  9. 转:【专题十二】实现一个简单的FTP服务器

    引言: 休息一个国庆节后好久没有更新文章了,主要是刚开始休息完心态还没有调整过来的, 现在差不多进入状态了, 所以继续和大家分享下网络编程的知识,在本专题中将和大家分享如何自己实现一个简单的FTP服务 ...

  10. 使用gitblit搭建一个简单的局域网服务器

    使用gitblit搭建一个简单的局域网服务器 1.使用背景 现在很多使用github管理代码,但是github需要互联网的支持,而且私有的git库需要收费.有一些项目的代码不能外泄,所以,搭建一个局域 ...

随机推荐

  1. P9933 [NFLSPC #6] 9.pop_book(); 题解

    题目链接: P9933 [NFLSPC #6] 9.pop_book(); 先考虑一个最基本的式子: \(x=v \times t\),很显然的一点是,除了 Alek岁,每个人的运动路程函数写出来都是 ...

  2. P9816 少项式复合幂 题解

    题目链接:少项式复合幂 注意到题目的模并不是很大,我们考虑两个核心的性质. \(f(f(x)) \bmod p=f(f(x) \bmod p) \bmod p\),证明直接代入 \(f(x)\) 进去 ...

  3. 神经网络优化篇:详解测试时的 Batch Norm(Batch Norm at test time)

    Batch归一化将的数据以mini-batch的形式逐一处理,但在测试时,可能需要对每个样本逐一处理,来看一下怎样调整的网络来做到这一点. 回想一下,在训练时,这些就是用来执行Batch归一化的等式. ...

  4. AutoGPT是什么?超简单安装使用教程

    1.AutoGPT 最近几天当红炸子鸡的是AutoGPT,不得不说AI发展真快啊,几天出来一个新东西,都跟不上时代的脚步了. AutoGPT是一个开源的应用程序,展示了GPT-4语言模型的能力.这个程 ...

  5. Hadoop组件兼容性

    (1)HBase和Hadoop.zookeeper.JDK兼容版本 参考网址: https://hbase.apache.org/book.html 1)JDK和Hbase的兼容版本  对于JDK,最 ...

  6. 程序员减少BUG的两个小妙招!

    原创:陶朱公Boy(微信公众号ID:taozhugongboy),欢迎分享,转载请保留出处. ​ 点评: 我们说衡量一个程序员水平的高低往往有很多因素,但有一个因素至关重要即代码质量. 如果程序员写的 ...

  7. Power BI 2 DAY

    目录 Power BI零散知识点 M函数 Power BI零散知识点 纵向合并 = 主页-组合-追加查询-追加查询(修改数据源)-将查询追加为信查询(创建新数据源) 横向合并 = 主页-组合-合并查询 ...

  8. 【Unity3D】刚体组件Rigidbody

    1 前言 ​ 刚体(Rigidbody)是运动学(Kinematic)中的一个概念,指在运动中和受力作用后,形状和大小不变,而且内部各点的相对位置不变的物体.在 Unity3D 中,刚体组件赋予了游戏 ...

  9. Laravel入坑指南(9)——数据迁移与填充

    当我们开发完成一个(小)项目,发布到线上时,我们需要将本地数据库迁移到服务器上,并且填充初始化数据.而Laravel框架规定了一套完善的数据迁移与填充机制. 在官网中分别介绍了以下四个命令: php ...

  10. An Introduction to ANYDATA

    以下内容来自Oracle FAQ writen By Kevin,关于ANYDATA类型在项目中的应用. My newest project needed to create a record kee ...