一个简单的RTMP服务器实现 --- RTMP复杂握手(Complex Handshake)
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);
后记
总结:
- 就是按照别人推断出来的东西进行代码实现,同时明确一下一些字段定义。
- 按照大佬文中评论,验证了一下,public_key和share_key对RTMP没有影响。
本系列文末总结
- 按照一个文档,实现了相关功能是一件有成就感的事情。
- 现在已经是2019年了,你要相信,你要做的事情,极有可能是别人做过的,可以去多查查资料以备参考。
- 静下心来做事,我发现有些时候我很浮躁。同时敢怀疑别人,并且思索怀疑的理由。
参考文献
- 无
打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)
PS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。
一个简单的RTMP服务器实现 --- RTMP复杂握手(Complex Handshake)的更多相关文章
- 自己动手模拟开发一个简单的Web服务器
开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此事要躬行”,于是我们自己模拟一个简单的W ...
- 一个简单的web服务器
写在前面 新的一年了,新的开始,打算重新看一遍asp.net本质论这本书,再重新认识一下,查漏补缺,认认真真的过一遍. 一个简单的web服务器 首先需要引入命名空间: System.Net,关于网络编 ...
- [置顶] 在Ubuntu下实现一个简单的Web服务器
要求: 实现一个简单的Web服务器,当服务器启动时要读取配置文件的路径.如果浏览器请求的文件是可执行的则称为CGI程序,服务器并不是将这个文件发给浏览器,而是在服务器端执行这个程序,将它的标准输出发给 ...
- Tomcat剖析(二):一个简单的Servlet服务器
Tomcat剖析(二):一个简单的Servlet服务器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三) ...
- Tomcat剖析(一):一个简单的Web服务器
Tomcat剖析(一):一个简单的Web服务器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三):连接器 ...
- 自己模拟的一个简单的web服务器
首先我为大家推荐一本书:How Tomcat Works.这本书讲的很详细的,虽然实际开发中我们并不会自己去写一个tomcat,但是对于了解Tomcat是如何工作的还是很有必要的. Servlet容器 ...
- java实现一个简单的Web服务器
注:本段内容来源于<JAVA 实现 简单的 HTTP服务器> 1. HTTP所有状态码 状态码 状态码英文名称 中文描述 100 Continue 继续.客户端应继续其请求 101 Swi ...
- 响应式编程笔记三:一个简单的HTTP服务器
# 响应式编程笔记三:一个简单的HTTP服务器 本文我们将继续前面的学习,但将更多的注意力放在用例和编写实际能用的代码上面,而非基本的APIs学习. 我们会看到Reactive是一个有用的抽象 - 对 ...
- 转:【专题十二】实现一个简单的FTP服务器
引言: 休息一个国庆节后好久没有更新文章了,主要是刚开始休息完心态还没有调整过来的, 现在差不多进入状态了, 所以继续和大家分享下网络编程的知识,在本专题中将和大家分享如何自己实现一个简单的FTP服务 ...
- 使用gitblit搭建一个简单的局域网服务器
使用gitblit搭建一个简单的局域网服务器 1.使用背景 现在很多使用github管理代码,但是github需要互联网的支持,而且私有的git库需要收费.有一些项目的代码不能外泄,所以,搭建一个局域 ...
随机推荐
- 如何修改OSW图表中显示的主机名称
本次测试的OSW版本:831 有人可能会说这种需求是吃饱了撑的吗,谁没事儿改这个名称干嘛啊? 其实并不是,因为有些生产案例非常典型,分享讲解时也需要配合OSW的趋势图来展示,但是出于保护客户隐私(哪怕 ...
- 24.3 向量化异常VEH--《Windows核心编程》
Windows 提供了向量化异常处理(vectored excepation handing,VEH)机制.程序可以注册一个函数,每当异常发送或者一个未处理异常脱离标准SEH的控制时,这个函数就会被调 ...
- .NET Core开发实战(第30课:领域事件:提升业务内聚,实现模块解耦)--学习笔记
30 | 领域事件:提升业务内聚,实现模块解耦 我们在领域的抽象层定义了领域事件和领域事件处理的接口 IDomainEvent namespace GeekTime.Domain { public i ...
- Numpy基本使用方法
Numpy基本使用方法 第一节 创建数组 import numpy as np import random # 创建数组 a = [1, 2, 3, 4, 5] a1 = np.array(a) pr ...
- JS Leetcode 74. 搜索二维矩阵题解分析,二分法与坐标轴法
壹 ❀ 引 本题来自Leetcode74. 搜索二维矩阵,虽然难度是中等,但如果站在做出来的角度,你会发现其实并不难,题目描述如下: 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值. ...
- NC25064 [USACO 2007 Mar G]Ranking the Cows
题目链接 题目 题目描述 Each of Farmer John's N cows (1 ≤ N ≤ 1,000) produces milk at a different positive rate ...
- 【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)
目录 前言 一.代码优化 1.1Stream 流自分页 1.2数据库分页 1.3其它思考 二.硬件配置 2.1云服务器配置 三.文章小结 前言 OOM 几乎是笔者工作中遇到的线上 bug 中最常见的, ...
- Windows下,SpringBoot JDBC无法连接的问题
问题症状 在Win7和Win10下启动时均会出现下面的错误,但是在OSX和Linux下没问题 com.mysql.jdbc.exceptions.jdbc4.CommunicationsExcepti ...
- Clock题解
Clock 题意:给一些时间,24小时制,给一个初始出发时间,问在钟表上最少转多少度能把所有给的时间都经历一遍. 思路:分四种情况模拟. 注意: 求的是度数,所以最后要乘6转换. 3:00,转到15: ...
- OpenCV开发笔记(六十二):红胖子8分钟带你深入了解亚像素角点检测(图文并茂+浅显易懂+程序源码)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...