【流媒体】RTMPDump—RTMP_Connect函数(握手、网络连接)

news2024/11/13 9:14:10

目录

  • 1. RTMP_Connect函数
    • 1.1 网络层连接(RTMP_Connect0)
    • 1.2 RTMP连接(RTMP_Connect1)
      • 1.2.1 握手(HandShake)
      • 1.2.2 RTMP的NetConnection(SendConnectPacket)
  • 2.小结

RTMP协议相关:
【流媒体】RTMP协议概述
【流媒体】RTMP协议的数据格式
【流媒体】RTMP协议的消息类型
【流媒体】RTMPDump—主流程简单分析
【流媒体】RTMPDump—RTMP_Connect函数(握手、网络连接)
【流媒体】RTMPDump—RTMP_ConnectStream(创建流连接)
【流媒体】RTMPDump—Download(接收流媒体信息)
【流媒体】RTMPDump—AMF编码
【流媒体】基于libRTMP的H264推流器

参考雷博的系列文章(可以从一篇链接到其他文章):
RTMPdump 源代码分析 1: main()函数

前面进行了RTMPDump主流程的分析,包括初始化和一些解析过程,现在分析RTMPDump是如何进行握手和网络连接,这是进行RTMP通信的第一步

1. RTMP_Connect函数

函数首先添加连接的地址,如果设置socksport,则使用socks连接,否则直接连接;随后,调用了RTMP_Connect0()和RTMP_Connect1()两个函数实现连接

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连接

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)

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中关于非加密握手的代码,如下所示

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,如下所示

#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()将信息发送出去

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来发送了

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进行了正式的连接,后续可以进行互相传输信息了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2058375.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实际开发中的模块化开发 - 模块间通讯(以直播间为例)

实际开发中的模块化开发 - 模块管理&#xff08;以直播间为例&#xff09;-CSDN博客 引言 在之前的博客中&#xff0c;我们讨论了模块化开发的概念、使用场景及其优势&#xff0c;并通过简单的案例实现了一个基础的模块化结构。我们创建了用户卡片模块和礼物展示模块&#xf…

同样的东西,京东贵多了,为啥还有人选择京东呢?

现在很少有商品&#xff0c;只在一个平台上出售了&#xff0c;几乎哪个平台都能买到。 那为什么京东贵多了&#xff0c;还有人去京东买&#xff1f; 小编就以自己的实际体验来说一说。 先看个案例&#xff1a; 小编去年在京东自营店买了一块西数的机械硬盘&#xff0c;用了…

PHP网上花店管理系统—计算机毕业设计源码无偿分享可私信21170

目 录 摘要 1 绪论 1.1研究背景 1.2项目背景 1.3 Thinkphp框架介绍 1.4论文结构与章节安排 2 网上花店管理系统系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1数据增加流程 2.2.2数据修改流程 2.2.3数据删除流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非…

怎样更改电脑的MAC地址?

怎样更改电脑的MAC地址&#xff1f; 电脑的机器码是可以修改的。 操作步骤&#xff1a; 1、通过按WINR键&#xff0c;调来电脑的接运行窗口&#xff0c;打开CMD命令来查看机器码。 2、命令提示符窗口里输入ipconfig /all&#xff0c;回车&#xff0c;即可显示出当前电脑的网…

ARM——操作示例

操作流程: 一、实现一个led亮灯 &#xff08;1&#xff09;GPIO&#xff1a;可编程的输入输出引脚 每一组io都有一个寄存GP*CON控制引脚作用&#xff0c;每个io都有2个位&#xff0c;控制引脚作用 每一组io都有一个寄存GP*DAT控制引脚数据&#xff0c;每个io都有1个位&a…

电脑硬盘坏了怎么恢复数据?

在数字化时代&#xff0c;电脑硬盘作为存储核心&#xff0c;承载着我们的工作文档、学习资料、家庭照片以及无数珍贵的回忆。然而&#xff0c;硬盘作为机械设备&#xff0c;也有其寿命和脆弱性&#xff0c;一旦出现故障&#xff0c;数据恢复便成为了一个紧迫而棘手的问题。本文…

【小趴菜前端学习日记3】

学习项目 一、深度&#xff08;穿透&#xff09;选择器1. /deep/2.>>>3. ::v-deep 二、vue-particles1.安装2.全局引入3.使用 三、v-bind对于样式控制的增强之操作类名class四、CryptoJs加密五、自定义指令的封装和使用防抖 六、mixins七、复制字段vue-clipboard复制文…

复制与引用

复制 复制有复制的特点。 复制可以将不可思议的巧合转变成必然。 假设基于很大的运气成分&#xff0c;探索出了一个执行流程。如果没有任何记录&#xff0c;那么下次再复现出这个流程&#xff0c;会需要同样的运气&#xff0c;甚至可能更多。但运气并不会总是发生的&#xff0c…

微服务注册中心

目录 一、微服务的注册中心 1、注册中心的主要作用 &#xff08;1&#xff09;服务发现 &#xff08;2&#xff09;服务配置 &#xff08;3&#xff09;服务健康检测 2、 常见的注册中心 二、nacos简介 1、nacos实战入门 &#xff08;1&#xff09;搭建nacos环境 &am…

20240821 每日AI必读资讯

&#x1f3ae;《黑神话&#xff1a;悟空》震撼上线&#xff0c;英伟达AI技术立功&#xff01; - 中国游戏史上的奇迹&#xff1a;《黑神话&#xff1a;悟空》预售销售额达3.9亿元&#xff0c;刷新国产游戏预售纪录。 - 游戏美学效果惊人&#xff1a;孙悟空形象深入人心&#…

Bootstrap 插件概览

在前面 布局组件 章节中所讨论到的组件仅仅是个开始。Bootstrap 自带 12 种 jQuery 插件&#xff0c;扩展了功能&#xff0c;可以给站点添加更多的互动。即使您不是一名高级的 JavaScript 开发人员&#xff0c;您也可以着手学习 Bootstrap 的 JavaScript 插件。利用 Bootstrap …

【重磅】WHO推荐的2024-2025年流感疫苗株组分更新了,快来看看有哪些变化吧?

前 言&#xff1a; 流感病毒会引起季节性流感&#xff0c;甚至有可能引起大流行暴发。流感病毒是负链RNA病毒&#xff0c;其分类复杂&#xff0c;亚型众多&#xff0c;容易突变。目前公认的预防流感的最佳方法是接种疫苗。为了保证疫苗的有效性&#xff0c;世界卫生组织&#…

【SAP HANA 41】HANA中函数 COUNT(DISTINCT(xxx)) 的方式使用

目录 一、语法 二、COUNT(*) 三、COUNT( [ ALL ] ) 四、COUNT(DISTINCT ) 在SAP HANA数据库中,COUNT 函数用于计算表中行的数量或者特定列中非NULL值的数量。你提到的语法是COUNT函数的不同用法,它们允许你根据需要对数据进行计数。下面是对每种用法的解释以及示例。 一…

路由高阶用法 Vue2

1.几个注意点 Home.vue <template><div><h2>我是Home内容</h2><ul class"nav nav-tabs"><li class"nav-item"><router-link class"nav-link" active-class"active" to"/home/news"…

TilesetLaye存在时,使用mask遮罩层,会出现锯齿的解决方案

TilesetLaye存在时&#xff0c;使用mask遮罩层&#xff0c;会出现锯齿 function addDemoGeoJsonLayer1() {const tiles3dLayer new mars3d.layer.TilesetLayer({name: "合肥市建筑物",url: "//data.mars3d.cn/3dtiles/jzw-hefei/tileset.json",maximumSc…

SparkSQL数据类型

支持的数据类型 SparkSQL支持的数据类型如下&#xff1a; 数值类型 ByteType&#xff1a;表示1字节带符号整数&#xff08;“带符号”意味着它可以表示正数和负数。&#xff09;。数字的范围是-128到127。ShortType&#xff1a;表示2字节带符号整数。数字的范围是-32768到32…

打造更高效的项目:如何选择合适的管理工具

国内外主流的 10 款项目工程管理系统对比&#xff1a;PingCode、Worktile、Asana、Trello、Monday.com、ClickUp、Wrike、泛微项目协同工具、广联达项目管理软件、泛普OA。 在选择项目工程管理系统时&#xff0c;你是否经常感到无从下手&#xff0c;担心投资不当或工具不适合自…

细数全球七大网络空间安全搜索引擎

随着网络攻击的频率和复杂性不断增加&#xff0c;安全专业人士需要利用各种工具来识别和应对潜在的威胁&#xff0c;网络安全搜索引擎就是其中之一&#xff0c;它们帮助安全专家查找漏洞、分析威胁情报以及监控互联网活动&#xff0c;本文将介绍全球七大网络安全搜索引擎。 1.…

误闯机器学习(第一关-概念和流程)

以下内容&#xff0c;皆为原创&#xff0c;实属不易&#xff0c;请各位帅锅&#xff0c;镁铝点点赞赞和关注吧&#xff01; 好戏开场了。 一.什么是机器学习 机器学习就是从数据中自动分析获取模型&#xff08;总结出的数据&#xff09;&#xff0c;并训练模型&#xff0c;去预…

Gadmin极速开发平台,几分钟给你整一个OA系统出来

Gadmin极速开发平台 在企业信息化的大潮中&#xff0c;Gadmin极速开发平台以其独特的低代码开发模式&#xff0c;为企业提供了一套高效、灵活的解决方案。本文将介绍Gadmin平台的基本信息、核心特点&#xff0c;以及它如何帮助企业快速实现信息化建设。 软件简介 Gadmin是一个…