RTMP简单握手实现
1.客户端握手的代码如下:
int SrsSimpleHandshake::handshake_with_server(SrsHandshakeBytes* hs_bytes, ISrsProtocolReaderWriter* io)
{
int ret = ERROR_SUCCESS;
ssize_t nsize;
// simple handshake
if ((ret = hs_bytes->create_c0c1()) != ERROR_SUCCESS) {
return ret;
}
if ((ret = io->write(hs_bytes->c0c1, 1537, &nsize)) != ERROR_SUCCESS) {
srs_warn("write c0c1 failed. ret=%d", ret);
return ret;
}
srs_verbose("write c0c1 success.");
if ((ret = hs_bytes->read_s0s1s2(io)) != ERROR_SUCCESS) {
return ret;
}
// plain text required.
if (hs_bytes->s0s1s2[0] != 0x03) {
ret = ERROR_RTMP_HANDSHAKE;
srs_warn("handshake failed, plain text required. ret=%d", ret);
return ret;
}
if ((ret = hs_bytes->create_c2()) != ERROR_SUCCESS) {
return ret;
}
// for simple handshake, copy s1 to c2.
// @see https://github.com/ossrs/srs/issues/418
memcpy(hs_bytes->c2, hs_bytes->s0s1s2 + 1, 1536);
if ((ret = io->write(hs_bytes->c2, 1536, &nsize)) != ERROR_SUCCESS) {
srs_warn("simple handshake write c2 failed. ret=%d", ret);
return ret;
}
srs_verbose("simple handshake write c2 success.");
srs_trace("simple handshake success.");
return ret;
}
1).调用hs_bytes->create_c0c1()创建c0c1,并发送c0c1给服务端
int SrsHandshakeBytes::create_c0c1()
{
int ret = ERROR_SUCCESS;
if (c0c1) {
return ret;
}
c0c1 = new char[1537];
srs_random_generate(c0c1, 1537);
// plain text required.
SrsBuffer stream;
if ((ret = stream.initialize(c0c1, 9)) != ERROR_SUCCESS) {
return ret;
}
stream.write_1bytes(0x03);
stream.write_4bytes((int32_t)::time(NULL));
stream.write_4bytes(0x00);
return ret;
}
2).客户端调用hs_bytes->read_s0s1s2(io)等待读取服务端发送来的s0s1s2
3).客户端读取完s0s1s2后,接着调用hs_bytes->create_c2创建c2,c2的第二个时间是从s1中拷贝的,创建完成后会发送c2给服务端
int SrsHandshakeBytes::create_c2()
{
int ret = ERROR_SUCCESS;
if (c2) {
return ret;
}
c2 = new char[1536];
srs_random_generate(c2, 1536);
// time
SrsBuffer stream;
if ((ret = stream.initialize(c2, 8)) != ERROR_SUCCESS) {
return ret;
}
stream.write_4bytes((int32_t)::time(NULL));
// c2 time2 copy from s1
if (s0s1s2) {
stream.write_bytes(s0s1s2 + 1, 4);
}
return ret;
}
int SrsSimpleHandshake::handshake_with_client(SrsHandshakeBytes* hs_bytes, ISrsProtocolReaderWriter* io)
{
int ret = ERROR_SUCCESS;
ssize_t nsize;
if ((ret = hs_bytes->read_c0c1(io)) != ERROR_SUCCESS) {
return ret;
}
// plain text required.
if (hs_bytes->c0c1[0] != 0x03) {
ret = ERROR_RTMP_PLAIN_REQUIRED;
srs_warn("only support rtmp plain text. ret=%d", ret);
return ret;
}
srs_verbose("check c0 success, required plain text.");
if ((ret = hs_bytes->create_s0s1s2(hs_bytes->c0c1 + 1)) != ERROR_SUCCESS) {
return ret;
}
if ((ret = io->write(hs_bytes->s0s1s2, 3073, &nsize)) != ERROR_SUCCESS) {
srs_warn("simple handshake send s0s1s2 failed. ret=%d", ret);
return ret;
}
srs_verbose("simple handshake send s0s1s2 success.");
if ((ret = hs_bytes->read_c2(io)) != ERROR_SUCCESS) {
return ret;
}
srs_trace("simple handshake success.");
return ret;
}
1).调用hs_bytes->read_c0c1(io)等待读取客户端发送来的c0,c1。
其中s1的第二个时间是从c1中拷贝的,s2是直接拷贝的c1。
s0s1s2创建完成后会发送给客户端。
int SrsHandshakeBytes::create_s0s1s2(const char* c1)
{
int ret = ERROR_SUCCESS;
if (s0s1s2) {
return ret;
}
s0s1s2 = new char[3073];
srs_random_generate(s0s1s2, 3073);
// plain text required.
SrsBuffer stream;
if ((ret = stream.initialize(s0s1s2, 9)) != ERROR_SUCCESS) {
return ret;
}
stream.write_1bytes(0x03);
stream.write_4bytes((int32_t)::time(NULL));
// s1 time2 copy from c1
if (c0c1) {
stream.write_bytes(c0c1 + 1, 4);
}
// if c1 specified, copy c1 to s2.
// @see: https://github.com/ossrs/srs/issues/46
if (c1) {
memcpy(s0s1s2 + 1537, c1, 1536);
}
return ret;
}
3).服务端调用hs_bytes->read_c2(io)等待读取客户端发送来的c2
包结构说明
c0 s0包结构:
只有8个bit占用1字节,双方通过这个命令来同步版本号,现在版本是03.
c1 s1包结构
这两个包第一个time是双方各自发出的time,c1包zero的4个字节都为0,s1 包中zero的四个字节使用从c1中获取的time。
c2 s2包结构
c2,s2是有发送顺序的,必须是s2首先发出,然后c2才可以发出。
RTMP复杂握手说明
转载:http://blog.csdn.net/win_lin/article/details/13006803
当服务器和客户端的握手是按照rtmp协议进行,是不支持h264/aac的,有数据,就是没有视频和声音。
原因是adobe变更了握手的数据结构,标准rtmp协议的握手的包是随机的1536字节(S1S2C1C2),变更后的是需要进行摘要和加密。
rtmp协议定义的为simple handshake,变更后加密握手可称为complex handshake。
本文详细分析了rtmpd(ccrtmpserver)中的处理逻辑,以及rtmpdump的处理逻辑,从一个全是魔法数字的世界找到他们的数据结构和算法。
complex handshake将C1S1分为4个部分,它们的顺序(schema)一种可能是:
客户端来决定使用哪种schema,服务器端则需要先尝试按照schema0解析,失败则用schema1解析。如下图所示:
无论key和digest位置如何,它们的结构是不变的:
如下图所示:
crtmp中这些全是魔法数字。
C2S2主要是提供对C1S1的验证。结构如下:
C2S2的结构相对比较简单。如下图所示:
下面介绍C1S1C2S2的生成以及验证算法。
C1S1中都是包含32字节的digest,而且digest将C1S1分成两部分:
如下图所示:
在生成C1时,需要用到c1s1-part1和c1s1-part2这两个部分的字节拼接起来的字节,定义为:
也就是说,把1536字节的c1s1中的32字节的digest拿剪刀剪掉,剩下的头和尾加在一起就是c1s1-joined。
用到的两个常量FPKey和FMSKey:
C1S1的算法完毕。
C2S2的生成算法如下: