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

目录

  • 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;}
#elseRTMP_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的NetConnectionif (!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 = 1536char serversig[RTMP_SIG_SIZE];// 1. C0数据报// 0x03表示客户端所期望的版本号,即C0clientbuf[0] = 0x03;		/* not encrypted */// 2. C1数据报// 获取当前的timestamp并且转换成为大端存储uptime = htonl(RTMP_GetTime());// 将时间戳拷贝到clientsig当中memcpy(clientsig, &uptime, 4);// 填充4个字节的0值memset(&clientsig[4], 0, 4);#ifdef _DEBUGfor (i = 8; i < RTMP_SIG_SIZE; i++)clientsig[i] = 0xff;
#elsefor (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 idpacket.m_nTimeStamp = 0;packet.m_nInfoField2 = 0;packet.m_hasAbsTimestamp = 0;packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;	// body = 4096 - 18enc = 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 10 1 2 3 4 5 6 7+---------------+|fmt|  cs id    | +---------------+(b) type 20				10 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +---------------+-------------+|fmt|     0     | cs id - 64  |+---------------+-------------+(c) type 30				1               20 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               30 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               30 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 sizenSize = 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 = 6hend = 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 sizeif (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 timestampif (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位设置为fmtswitch (cSize){// 低6位设置成为stream idcase 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,不需要分成多个chunknChunkSize = 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;}// 更新sizenSize -= 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 0000if (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);}}// 存储前面发送的packetif (!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.rhkb.cn/news/405799.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024计算机软考报名流程(电脑报名)

1.24年下半年软考报名时间&#xff0c;各省报名时间不一样&#xff0c; 报名时间大概集中在&#xff1a;24年8月19日&#xff5e;24年9月15日&#xff1b; 报名网站&#xff1a;中国计算机技术职业资格网&#xff1b; 广东&#xff1a;2024年8月21日9:00至29日17:00 安徽&#…

Vue3 的 expose 介绍

在 Vue 3 中&#xff0c;expose 是一个用于控制组件内部方法和属性暴露给父组件的新功能。这使得父组件可以调用子组件内部的方法或访问其数据&#xff0c;尤其在使用组合式 API&#xff08;Composition API&#xff09;时&#xff0c;这种能力非常有用。 1. 基本用法 expose…

[PHP]-Laravel中Group By引发的问题思考

Laravel 和 ThinkPHP 是两个不同的 PHP 框架&#xff0c;它们在底层使用了相同的 SQL 查询语言来与数据库交互。然而&#xff0c;由于框架的设计和实现方式不同&#xff0c;它们在生成 SQL 查询时可能会表现出一些细微的差异&#xff0c;包括对 GROUP BY 子句的处理。 在调用查…

高性能web服务器1

基础 Web 服务简介 Web 服务是互联网的核心组成部分之一&#xff0c;它允许用户通过浏览器访问信息和应用程序。一个基础的 Web 服务通常由 Web 服务器软件、静态网页内容、以及可选的动态内容生成程序组成。 Web 服务器软件 Web 服务器软件是运行在服务器上的程序&#xff…

STM32(二):GPIO

GPIO(General Purpose Input Output)通用输入输出口 1.可配置为8种输入输出模式&#xff0c;引脚电平:0V~3.3V&#xff0c;部分引脚可容忍5V&#xff0c;输出模式下可控制端口输出高低电平&#xff0c;用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等&#xff0c;输入模式下…

二叉树中的奇偶树问题

目录 一题目&#xff1a; 二思路汇总&#xff1a; 1.二叉树层序遍历&#xff1a; 1.1题目介绍&#xff1a; 1.2 解答代码&#xff08;c版&#xff09;&#xff1a; 1.3 解答代码&#xff08;c版&#xff09;&#xff1a; 1.4 小结一下&#xff1a; 2.奇偶树分析&#xf…

glibc 2.24 下 IO_FILE 的利用

文章目录 glibc 2.24 下 IO_FILE 的利用介绍&#xff1a;新的利用技术fileno 与缓冲区的相关利用实例&#xff1a;1. _IO_str_jumps -> overflow实例&#xff1a; 2. _IO_str_jumps -> finish实例: 最后拓展一下上一篇博客house of orange题目的做法: glibc 2.24 下 IO_F…

Oracle基本SQL操作-用户角色权限管理

一、用户权限管理 -- 创建锁定用户&#xff0c;此时用户不可用 create USER zhucl IDENTIFIED BY 123456 account lock; 会提示用户被锁定&#xff1a; -- 删除用户 drop user zhucl;-- 重新创建用户&#xff0c;不锁定 create user zhucl IDENTIFIED BY 123456 account unlo…

嵌入式和单片机有什么区别?

目录 &#xff08;1&#xff09;什么是嵌入式&#xff1f; &#xff08;2&#xff09;什么是单片机&#xff1f; &#xff08;3&#xff09;嵌入式和单片机的共同点 &#xff08;4&#xff09;嵌入式和单片机的区别 &#xff08;1&#xff09;什么是嵌入式&#xff1f; 关…

45.x86游戏实战-XXX封包组包拼包详解

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

提车后遇大降价被指“背刺”车主,方程豹的口碑问题何解?

进入8月下旬&#xff0c;汽车市场“金九银十”的销售旺季即将到来&#xff0c;将行业“内卷”推向新高峰。即便有宝马等高端豪华品牌退出“价格战”的先例&#xff0c;但为刺激销量&#xff0c;不少车企依旧推出了各式各样的价格优惠政策&#xff0c;行业内部价格竞争狼烟四起。…

Kotlin 流flow、ShareFlow、StateFlow、Channel的解释与使用

一、介绍 随着Android接入kotlin开发&#xff0c;Android之前好多模式也渐渐被kotlin替代。开发模式也在做渐进的转型&#xff0c;从MVC到MVP在到MVVP以及现在的MVI等。 流IO在java中和kotlin中使用率都是比较高的&#xff0c;场景很多。如Java的IO和NIO&#xff0c;再到我们现…

Java、python、php版的高校失物招领平台(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

kali网络代理设置

首先主机必须有自己的代理。记住主机的ip和代理端口。 在kali中打开终端&#xff1a; vim /etc/proxychains4.conf输入代理进行更改 把这行注释掉&#xff0c;在下一行输入 socks5 主机ip 代理端口 点击esc&#xff0c;在:wq退出保存。 配置完成。

Salesforce 发布开源大模型 xGen-MM

xGen-MM 论文 在当今 AI 技术飞速发展的时代&#xff0c;一个新的多模态 AI 模型悄然崛起&#xff0c;引起了业界的广泛关注。这个由 Salesforce 推出的开源模型—— xGen-MM&#xff0c;正以其惊人的全能特性和独特优势&#xff0c;在 AI 领域掀起一阵旋风。那么&#xff0c;x…

Why Does ChatGPT Fall Short in Providing Truthful Answers?

文章目录 题目摘要简介相关工作模型和数据集结果事实性背后的能力提高 QA 的事实性结论 题目 为什么 ChatGPT 无法提供真实的答案&#xff1f; 论文地址:https://arxiv.org/abs/2304.10513 摘要 ChatGPT 等大型语言模型的最新进展已显示出影响人类生活各个方面的巨大潜力。然而…

数据库学习(进阶)

数据库学习&#xff08;进阶&#xff09; Mysql结构:连接层&#xff1a;服务层&#xff08;核心层&#xff09;&#xff1a;存储引擎层&#xff1a;系统文件层&#xff1a; 存储引擎&#xff08;概述&#xff09;:存储引擎特点&#xff1a;InnoDB存储引擎&#xff1a;(为并发条…

【C++ 面试 - 面向对象】每日 3 题(二)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

C语言钥匙迷宫2.0

目录 开头程序程序的流程图程序游玩的效果结尾 开头 大家好&#xff0c;我叫这是我58。废话不多说&#xff0c;咱们直接开始。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <string.h> #include <Windows.h> enum color {Y,B,R …

裸金属服务器和裸金属云服务器:区别、优势与选择

首先&#xff0c;必须肯定的是&#xff1a;裸金属服务器和裸金属云服务器是有区别的。 ‌ 二者的概述 裸金属服务器&#xff08;‌Bare Metal Server&#xff09;‌是一种物理服务器&#xff0c;‌它直接在硬件上运行&#xff0c;‌没有额外的虚拟化层。‌这意味着每个应用程…