目录
- [1. RTMP_Connect函数](#1. RTMP_Connect函数)
-
- [1.1 网络层连接(RTMP_Connect0)](#1.1 网络层连接(RTMP_Connect0))
- [1.2 RTMP连接(RTMP_Connect1)](#1.2 RTMP连接(RTMP_Connect1))
-
- [1.2.1 握手(HandShake)](#1.2.1 握手(HandShake))
- [1.2.2 RTMP的NetConnection(SendConnectPacket)](#1.2.2 RTMP的NetConnection(SendConnectPacket))
- 2.小结
RTMP协议相关:
【流媒体】RTMP协议概述
【流媒体】RTMP协议的数据格式
【流媒体】RTMP协议的消息类型
【流媒体】RTMPDump---主流程简单分析
参考雷博的系列文章(可以从一篇链接到其他文章):
RTMPdump 源代码分析 1: main()函数
前面进行了RTMPDump主流程的分析,包括初始化和一些解析过程,现在分析RTMPDump是如何进行握手和网络连接,这是进行RTMP通信的第一步
1. RTMP_Connect函数
函数首先添加连接的地址,如果设置socksport,则使用socks连接,否则直接连接;随后,调用了RTMP_Connect0()和RTMP_Connect1()两个函数实现连接
c
int
RTMP_Connect(RTMP * r, RTMPPacket * cp)
{
struct sockaddr_in service;
if (!r->Link.hostname.av_len)
return FALSE;
memset(&service, 0, sizeof(struct sockaddr_in));
service.sin_family = AF_INET;
if (r->Link.socksport) // 如果设置了socksport,则通过socks连接
{
/* Connect via SOCKS */
if (!add_addr_info(&service, &r->Link.sockshost, r->Link.socksport))
return FALSE;
}
else // 否则直接连接
{
/* Connect directly */
if (!add_addr_info(&service, &r->Link.hostname, r->Link.port))
return FALSE;
}
// 网络层TCP连接
if (!RTMP_Connect0(r, (struct sockaddr*) & service))
return FALSE;
r->m_bSendCounter = TRUE;
// 建立RTMP连接
return RTMP_Connect1(r, cp);
}
1.1 网络层连接(RTMP_Connect0)
RTMP_Connect0实现了TCP连接功能,使得client和server在网络层能够进行通信,首先使用socket()函数初始化协议为TCP,随后使用connect()进行TCP连接
c
int
RTMP_Connect0(RTMP * r, struct sockaddr* service)
{
int on = 1;
r->m_sb.sb_timedout = FALSE;
r->m_pausing = 0;
r->m_fDuration = 0.0;
r->m_sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 初始化为TCP连接
if (r->m_sb.sb_socket != -1)
{
if (connect(r->m_sb.sb_socket, service, sizeof(struct sockaddr)) < 0) // 进行TCP连接
{
int err = GetSockError();
RTMP_Log(RTMP_LOGERROR, "%s, failed to connect socket. %d (%s)",
__FUNCTION__, err, strerror(err));
RTMP_Close(r);
return FALSE;
}
if (r->Link.socksport)
{
RTMP_Log(RTMP_LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__);
if (!SocksNegotiate(r))
{
RTMP_Log(RTMP_LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
}
}
else
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to create socket. Error: %d", __FUNCTION__,
GetSockError());
return FALSE;
}
/* set timeout */
{
SET_RCVTIMEO(tv, r->Link.timeout);
if (setsockopt
(r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)& tv, sizeof(tv)))
{
RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!",
__FUNCTION__, r->Link.timeout);
}
}
setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char*)& on, sizeof(on));
return TRUE;
}
1.2 RTMP连接(RTMP_Connect1)
该函数主要调用了两个函数:(1)握手(HandShake);(2)RTMP的NetConnection(SendConnectPacket)
c
int
RTMP_Connect1(RTMP * r, RTMPPacket * cp)
{
if (r->Link.protocol & RTMP_FEATURE_SSL)
{
#if defined(CRYPTO) && !defined(NO_SSL)
TLS_client(RTMP_TLS_ctx, r->m_sb.sb_ssl);
TLS_setfd(r->m_sb.sb_ssl, r->m_sb.sb_socket);
if (TLS_connect(r->m_sb.sb_ssl) < 0)
{
RTMP_Log(RTMP_LOGERROR, "%s, TLS_Connect failed", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
#else
RTMP_Log(RTMP_LOGERROR, "%s, no SSL/TLS support", __FUNCTION__);
RTMP_Close(r);
return FALSE;
#endif
}
if (r->Link.protocol & RTMP_FEATURE_HTTP)
{
r->m_msgCounter = 1;
r->m_clientID.av_val = NULL;
r->m_clientID.av_len = 0;
HTTP_Post(r, RTMPT_OPEN, "", 1);
if (HTTP_read(r, 1) != 0)
{
r->m_msgCounter = 0;
RTMP_Log(RTMP_LOGDEBUG, "%s, Could not connect for handshake", __FUNCTION__);
RTMP_Close(r);
return 0;
}
r->m_msgCounter = 0;
}
RTMP_Log(RTMP_LOGDEBUG, "%s, ... connected, handshaking", __FUNCTION__);
// 1. 握手
if (!HandShake(r, TRUE))
{
RTMP_Log(RTMP_LOGERROR, "%s, handshake failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
RTMP_Log(RTMP_LOGDEBUG, "%s, handshaked", __FUNCTION__);
// 2. RTMP的NetConnection
if (!SendConnectPacket(r, cp))
{
RTMP_Log(RTMP_LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
return TRUE;
}
1.2.1 握手(HandShake)
握手是RTMP协议实现的第一个步骤,需要重点分析。值得一提的是,在雷博记录的文章当中(RTMPdump(libRTMP)源代码分析 4: 连接第一步------握手(Hand Shake)),记录的应该是包含加密过程的握手函数,不包含加密过程的握手函数应该在rtmp.c中
前面自己记录的关于握手过程的文章为【流媒体】RTMP协议概述,握手的流程为
RTMPDump中关于非加密握手的代码,如下所示
c
static int
HandShake(RTMP * r, int FP9HandShake)
{
int i;
uint32_t uptime, suptime;
int bMatch;
char type;
// clientbuf当中包含了C0和C1数据报,并且同时发出去
char clientbuf[RTMP_SIG_SIZE + 1], *clientsig = clientbuf + 1; // RTMP_SIG_SIZE = 1536
char serversig[RTMP_SIG_SIZE];
// 1. C0数据报
// 0x03表示客户端所期望的版本号,即C0
clientbuf[0] = 0x03; /* not encrypted */
// 2. C1数据报
// 获取当前的timestamp并且转换成为大端存储
uptime = htonl(RTMP_GetTime());
// 将时间戳拷贝到clientsig当中
memcpy(clientsig, &uptime, 4);
// 填充4个字节的0值
memset(&clientsig[4], 0, 4);
#ifdef _DEBUG
for (i = 8; i < RTMP_SIG_SIZE; i++)
clientsig[i] = 0xff;
#else
for (i = 8; i < RTMP_SIG_SIZE; i++) // 填充1536 - 8 = 1528个随机字节
clientsig[i] = (char)(rand() % 256);
#endif
// 3. 发送C0和C1数据报
if (!WriteN(r, clientbuf, RTMP_SIG_SIZE + 1))
return FALSE;
// 4. 接收S0数据报,即server返回的可以使用的RTMP版本号
if (ReadN(r, &type, 1) != 1) /* 0x03 or 0x06 */
return FALSE;
RTMP_Log(RTMP_LOGDEBUG, "%s: Type Answer : %02X", __FUNCTION__, type);
// 检查client期望的RTMP版本号是否与server所支持的RTMP版本号匹配
if (type != clientbuf[0])
RTMP_Log(RTMP_LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d",
__FUNCTION__, clientbuf[0], type);
// 5. 接收S1数据报
if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
return FALSE;
/* decode server response */
// 解析接收数据报时间戳
memcpy(&suptime, serversig, 4);
suptime = ntohl(suptime);
RTMP_Log(RTMP_LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, suptime);
RTMP_Log(RTMP_LOGDEBUG, "%s: FMS Version : %d.%d.%d.%d", __FUNCTION__,
serversig[4], serversig[5], serversig[6], serversig[7]);
/* 2nd part of handshake */
// 6. 发送C2数据报
// 发送出去的C2数据报就是接收到的S1数据报
if (!WriteN(r, serversig, RTMP_SIG_SIZE))
return FALSE;
// 7. 读取S2数据报
if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
return FALSE;
// 检查S2数据报和C1数据报的timestamp是否匹配
bMatch = (memcmp(serversig, clientsig, RTMP_SIG_SIZE) == 0);
if (!bMatch)
{
RTMP_Log(RTMP_LOGWARNING, "%s, client signature does not match!", __FUNCTION__);
}
return TRUE;
}
代码基本就是按照标准来写的,唯一需要注意的是C0和C1数据报是同时发送的,C0和C1都存储在clientbuf当中
1.2.2 RTMP的NetConnection(SendConnectPacket)
该函数的主要作用是进行NetConnection,再回顾一下NetConnection的流程图
从流程图中看,client在进行握手成功之后,会向server发送一个 "connect" 的命令,申请进行RTMP连接,而SendConnectPacket实现的功能就是发送一个 "connect" 的命令。另外,也回顾一下connect命令当中可以携带的参数,这些参数在SendConnectPacket当中都有考虑到
RTMPDump会定义所需要使用的命令,例如connect命令会被定义成为 av_connect,如下所示
c
#define AVC(str) {str,sizeof(str)-1}
#define SAVC(x) static const AVal av_##x = AVC(#x)
// connect 命令中使用的名值对对象的描述
SAVC(app);
SAVC(connect); // connect命令,{"connet", sizeof(connect) - 1}
SAVC(flashVer);
SAVC(swfUrl);
SAVC(pageUrl);
SAVC(tcUrl);
SAVC(fpad);
SAVC(capabilities);
SAVC(audioCodecs);
SAVC(videoCodecs);
SAVC(videoFunction);
SAVC(objectEncoding);
SAVC(secureToken);
SAVC(secureTokenResponse);
SAVC(type);
SAVC(nonprivate);
SendConnectPacket函数用于发送一个connect命令,会写入connect当中可能会写入的参数,随后使用RTMP_SendPacket()将信息发送出去
c
static int
SendConnectPacket(RTMP * r, RTMPPacket * cp)
{
RTMPPacket packet;
// pend是尾缀
char pbuf[4096], * pend = pbuf + sizeof(pbuf);
char* enc;
if (cp)
return RTMP_SendPacket(r, cp, TRUE);
// 块流ID设置为3(似乎在标准文档中没有说是多少?)
packet.m_nChannel = 0x03; /* control channel (invoke) */
/*
#define RTMP_PACKET_SIZE_LARGE 0 //
#define RTMP_PACKET_SIZE_MEDIUM 1 //
#define RTMP_PACKET_SIZE_SMALL 2 //
#define RTMP_PACKET_SIZE_MINIMUM 3 //
*/
packet.m_headerType = RTMP_PACKET_SIZE_LARGE; // m_headerType对应于Basic Header中的fmt
// RTMP_PACKET_TYPE_INVOKE = 0x14 = 20,表明这是一条命令消息,并且以AMF0的格式进行编码
packet.m_packetType = RTMP_PACKET_TYPE_INVOKE; // m_packetType对应于Messge Header中的message type id
packet.m_nTimeStamp = 0;
packet.m_nInfoField2 = 0;
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; // body = 4096 - 18
enc = packet.m_body;
/*
+-----------------+-----------+-------------+------------+------------
| RTMP Max Header | Data Type | Data Length | Data Value | xxx
| (18 Bytes) | (1 Bytes) | (x Bytes) | (L Bytes) | (.. Bytes)
+-----------------+-----------+-------------+------------+------------
pbuf enc
(packet.m_body)
*/
// 下面会写入packet.m_body的信息,这些信息用于connect
// av_connect = { "connect", sizeof("connect") - 1 };
enc = AMF_EncodeString(enc, pend, &av_connect); // 将connect命令写入到enc中
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); // 将呼叫的次数写入到enc中
*enc++ = AMF_OBJECT; // 写入data类型为object
// 写入app信息
enc = AMF_EncodeNamedString(enc, pend, &av_app, &r->Link.app);
if (!enc)
return FALSE;
if (r->Link.protocol & RTMP_FEATURE_WRITE)
{
enc = AMF_EncodeNamedString(enc, pend, &av_type, &av_nonprivate);
if (!enc)
return FALSE;
}
if (r->Link.flashVer.av_len)
{
enc = AMF_EncodeNamedString(enc, pend, &av_flashVer, &r->Link.flashVer);
if (!enc)
return FALSE;
}
if (r->Link.swfUrl.av_len)
{
enc = AMF_EncodeNamedString(enc, pend, &av_swfUrl, &r->Link.swfUrl);
if (!enc)
return FALSE;
}
if (r->Link.tcUrl.av_len)
{
enc = AMF_EncodeNamedString(enc, pend, &av_tcUrl, &r->Link.tcUrl);
if (!enc)
return FALSE;
}
if (!(r->Link.protocol & RTMP_FEATURE_WRITE))
{
enc = AMF_EncodeNamedBoolean(enc, pend, &av_fpad, FALSE);
if (!enc)
return FALSE;
enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 15.0);
if (!enc)
return FALSE;
enc = AMF_EncodeNamedNumber(enc, pend, &av_audioCodecs, r->m_fAudioCodecs);
if (!enc)
return FALSE;
enc = AMF_EncodeNamedNumber(enc, pend, &av_videoCodecs, r->m_fVideoCodecs);
if (!enc)
return FALSE;
enc = AMF_EncodeNamedNumber(enc, pend, &av_videoFunction, 1.0);
if (!enc)
return FALSE;
if (r->Link.pageUrl.av_len)
{
enc = AMF_EncodeNamedString(enc, pend, &av_pageUrl, &r->Link.pageUrl);
if (!enc)
return FALSE;
}
}
if (r->m_fEncoding != 0.0 || r->m_bSendEncoding)
{ /* AMF0, AMF3 not fully supported yet */
enc = AMF_EncodeNamedNumber(enc, pend, &av_objectEncoding, r->m_fEncoding);
if (!enc)
return FALSE;
}
if (enc + 3 >= pend)
return FALSE;
*enc++ = 0;
*enc++ = 0; /* end of object - 0x00 0x00 0x09 */
*enc++ = AMF_OBJECT_END; // 写完object
/* add auth string */
// 写认证信息
if (r->Link.auth.av_len)
{
enc = AMF_EncodeBoolean(enc, pend, r->Link.lFlags & RTMP_LF_AUTH);
if (!enc)
return FALSE;
enc = AMF_EncodeString(enc, pend, &r->Link.auth);
if (!enc)
return FALSE;
}
if (r->Link.extras.o_num)
{
int i;
for (i = 0; i < r->Link.extras.o_num; i++)
{
enc = AMFProp_Encode(&r->Link.extras.o_props[i], enc, pend);
if (!enc)
return FALSE;
}
}
packet.m_nBodySize = enc - packet.m_body;
return RTMP_SendPacket(r, &packet, TRUE);
}
RTMP_SendPacket()的实现如下所示,基本就是按照标准文档来写入chunk信息并发送,关键位置有注释,大体的步骤为:
(1)确定头信息内容
(a)确定message header size
(b)确定basic header size
(c)确定extended timestamp
(2)写入头信息内容
(a)写入basic header
(b)写入message header中的timestamp(3字节)
(c)写入bodySize(即msg length)(3字节)
(d)写入packetType(即msg type id)(1字节)
(e)写入最后4字节(即message stram id)(4字节)
(f)写入扩展的时间戳(4字节)
(3)发送信息
关于代码的实现,这里有一个小点:
在代码中,是以chunk的格式来存储所有格式的,而不是以message的格式,但最后还有一个分包的操作。按理来说,以message格式存储才需要分包,这里相当于是把一个大的chunk分成了多个小的chunk来发送了
c
int
RTMP_SendPacket(RTMP * r, RTMPPacket * packet, int queue)
{
const RTMPPacket* prevPacket;
uint32_t last = 0;
int nSize;
int hSize, cSize;
char* header, * hptr, * hend, hbuf[RTMP_MAX_HEADER_SIZE], c;
uint32_t t;
char* buffer, * tbuf = NULL, * toff = NULL;
int nChunkSize;
int tlen;
// 检查channel数量
if (packet->m_nChannel >= r->m_channelsAllocatedOut)
{
int n = packet->m_nChannel + 10;
RTMPPacket** packets = realloc(r->m_vecChannelsOut, sizeof(RTMPPacket*) * n);
if (!packets) {
free(r->m_vecChannelsOut);
r->m_vecChannelsOut = NULL;
r->m_channelsAllocatedOut = 0;
return FALSE;
}
r->m_vecChannelsOut = packets;
memset(r->m_vecChannelsOut + r->m_channelsAllocatedOut, 0, sizeof(RTMPPacket*) * (n - r->m_channelsAllocatedOut));
r->m_channelsAllocatedOut = n;
}
prevPacket = r->m_vecChannelsOut[packet->m_nChannel];
/*
Chunk Format:
+--------------+----------------+--------------------+-----------------
| Basic Header | Message Header | Extended Timestamp | Chunk Data....
+--------------+----------------+--------------------+-----------------
|<----------------- Chunk Header ------------------->|
(1) Basic Header
(a) type 1
0 1 2 3 4 5 6 7
+---------------+
|fmt| cs id |
+---------------+
(b) type 2
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+---------------+-------------+
|fmt| 0 | cs id - 64 |
+---------------+-------------+
(c) type 3
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+---------------+------------------------------+
|fmt| 1 | cs id - 64 |
+---------------+------------------------------+
(2) Message Header
(a) type 0 (11 bytes)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+----------------------------------------------+---------------+
| timestamp | message length|
+----------------------------------------------+---------------+
| message length (cont) | message type id| msg stream id |
+----------------------------------------------+---------------+
| message stream id(cont) |
+----------------------------------------------+
(b) type 1 (7 bytes)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+----------------------------------------------+---------------+
| timestamp delta | message length|
+----------------------------------------------+---------------+
| message length (cont) | message type id|
+----------------------------------------------+
(c) type 2 (3 bytes)
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+----------------------------------------------+
| timestamp delta |
+----------------------------------------------+
(d) type 3 (no message header)
*/
// 1. 确定头信息内容
// m_headerType对应于BasicHeader中的fmt字段,表示后续msg的格式
// m_packetType对应于Message Header中的message type id,表示消息的类型
if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)
{
// 前面packet的数据是否和当前packet数据有相同之处,如果有,则去掉当前packet的冗余信息
/* compress a bit by using the prev packet's attributes */
if (prevPacket->m_nBodySize == packet->m_nBodySize
&& prevPacket->m_packetType == packet->m_packetType
&& packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM)
packet->m_headerType = RTMP_PACKET_SIZE_SMALL;
if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp
&& packet->m_headerType == RTMP_PACKET_SIZE_SMALL)
packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM;
last = prevPacket->m_nTimeStamp;
}
if (packet->m_headerType > 3) /* sanity */
{
RTMP_Log(RTMP_LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.",
(unsigned char)packet->m_headerType);
return FALSE;
}
// static const int packetSize[] = { 12, 8, 4, 1 };
/*
问:标准当中的msg header size为11, 7, 3, 0,为什么在这里加1?
答:RTMP数据报中包括Basic Header、Message Header和Chunk Data,其中Basic Header由fmt和chunk stream id组成,
fmt占据2比特,chunk stream id指示当前chunk在当前stream当中位于第几个位置,如果是 (2, 63],则为1字节,这样packetSize就加1
*/
// nSize : message header size
// (1.a) 确定message header size
nSize = packetSize[packet->m_headerType];
hSize = nSize; cSize = 0;
t = packet->m_nTimeStamp - last;
if (packet->m_body)
{
header = packet->m_body - nSize; // header起始地址
hend = packet->m_body; // header结束地址
}
else
{
header = hbuf + 6; // header size = 6
hend = hbuf + sizeof(hbuf);
}
/*
stream_id的范围给出了chunk basic header的大小
+------------+--------------+
| range | Bytes |
+------------+--------------+
| (2, 63] | 1 byte |
+------------+--------------+
| (63, 319] | 2 Bytes |
+------------+--------------+
|(319, 65599]| 3 Bytes |
+------------+--------------+
*/
// chunk stream id的检查
// cSize描述Basic header的大小
// (1.b) 确定basic header size
if (packet->m_nChannel > 319)
cSize = 2; // chunk basic header为3个字节
else if (packet->m_nChannel > 63)
cSize = 1; // chunk basic header为2个字节
if (cSize)
{
header -= cSize;
hSize += cSize;
}
// (1.c) 确定extended timestamp
if (t >= 0xffffff) // 时间戳过大,需要增加额外的extended timestamp
{
header -= 4;
hSize += 4;
RTMP_Log(RTMP_LOGWARNING, "Larger timestamp than 24-bit: 0x%x", t);
}
// 2. 写入头信息
hptr = header;
c = packet->m_headerType << 6; // 高2位设置为fmt
switch (cSize)
{
// 低6位设置成为stream id
case 0:
c |= packet->m_nChannel;
break;
case 1:
break;
case 2:
c |= 1;
break;
}
// (2.a) 写入basic header
*hptr++ = c;
// 如果cSize不为0,说明chunk basic header为2字节或者3字节,需要写入0值或者1值
if (cSize)
{
int tmp = packet->m_nChannel - 64;
*hptr++ = tmp & 0xff;
if (cSize == 2)
* hptr++ = tmp >> 8;
}
// (2.b) 写入message header中的timestamp,3字节
if (nSize > 1)
{
// 用于编码24位整数值,将其从主机字节序转换为AMF格式所使用的网络字节序
hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t);
}
if (nSize > 4)
{
// (2.c) 写入bodySize(msg length),3字节
hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize);
// (2.d) 写入packetType(msg type id),1字节
*hptr++ = packet->m_packetType;
}
// (2.e) 写入最后4字节 (message stram id)
if (nSize > 8) // 将一个整数以小端序(little-endian)的方式进行编码为32位的整数
hptr += EncodeInt32LE(hptr, packet->m_nInfoField2);
// (2.f) 写入扩展的时间戳,4字节
if (t >= 0xffffff) // 用于编码32位整数值
hptr = AMF_EncodeInt32(hptr, hend, t);
nSize = packet->m_nBodySize;
buffer = packet->m_body;
nChunkSize = r->m_outChunkSize; // 默认的chunk大小为128个字节
RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, r->m_sb.sb_socket,
nSize);
/* send all chunks in one HTTP request */
if (r->Link.protocol & RTMP_FEATURE_HTTP)
{
int chunks = (nSize + nChunkSize - 1) / nChunkSize;
if (chunks > 1)
{
tlen = chunks * (cSize + 1) + nSize + hSize;
tbuf = malloc(tlen);
if (!tbuf)
return FALSE;
toff = tbuf;
}
}
// 3. 发送消息
// 前面已经将所需要的信息写入到了packet中,现在需要将packet分成多个chunk发送出去
while (nSize + hSize) // nSize = bodySize, hSize = headerSize;
{
int wrote;
if (nSize < nChunkSize) // 当前剩余的size,不需要分成多个chunk
nChunkSize = nSize;
RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t*)header, hSize);
RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t*)buffer, nChunkSize);
if (tbuf)
{
memcpy(toff, header, nChunkSize + hSize);
toff += nChunkSize + hSize;
}
else
{
// 发出信息,大小为nChunkSize + hSize
// nChunkSize默认为128字节
wrote = WriteN(r, header, nChunkSize + hSize);
if (!wrote)
return FALSE;
}
// 更新size
nSize -= nChunkSize;
buffer += nChunkSize;
hSize = 0; // 第一次就会把header中的信息全部发完
if (nSize > 0) // 还有消息没有发送完,需要将剩余的信息打包成为chunk,用于后续的发送
{
header = buffer - 1;
hSize = 1;
// 重新处理头部信息
if (cSize)
{
header -= cSize;
hSize += cSize;
}
if (t >= 0xffffff)
{
header -= 4;
hSize += 4;
}
// 取出c中的前2位,即fmt信息
*header = (0xc0 | c); // 0xc0 : 1100 0000
if (cSize)
{
int tmp = packet->m_nChannel - 64;
header[1] = tmp & 0xff;
if (cSize == 2)
header[2] = tmp >> 8;
}
if (t >= 0xffffff)
{
char* extendedTimestamp = header + 1 + cSize;
AMF_EncodeInt32(extendedTimestamp, extendedTimestamp + 4, t);
}
}
}
if (tbuf)
{
int wrote = WriteN(r, tbuf, toff - tbuf);
free(tbuf);
tbuf = NULL;
if (!wrote)
return FALSE;
}
/* we invoked a remote method */
if (packet->m_packetType == RTMP_PACKET_TYPE_INVOKE)
{
AVal method;
char* ptr;
ptr = packet->m_body + 1;
AMF_DecodeString(ptr, &method);
RTMP_Log(RTMP_LOGDEBUG, "Invoking %s", method.av_val);
/* keep it in call queue till result arrives */
if (queue) {
int txn;
ptr += 3 + method.av_len;
txn = (int)AMF_DecodeNumber(ptr);
AV_queue(&r->m_methodCalls, &r->m_numCalls, &method, txn);
}
}
// 存储前面发送的packet
if (!r->m_vecChannelsOut[packet->m_nChannel])
r->m_vecChannelsOut[packet->m_nChannel] = malloc(sizeof(RTMPPacket));
memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket));
return TRUE;
}
2.小结
记录了RTMPDump中如何以client的视角与对端server建立连接的过程,分为几个步骤:
(1)建立socket连接
(2)建立RTMP连接
(a)握手
(b)RTMP的网络连接(会发送connect命令)
基于此,RTMPDump的client就与server进行了正式的连接,后续可以进行互相传输信息了