/**
* @brief 建立RTMP中的网络连接(NetConnection).
* a) 创建并设置目标socket,包括ip地址和端口号
* b) 建立socket连接,设置socket的超时和接收、发送缓冲区的大小
* c) 握手操作
* d) 发送含有connect命令的数据报,用于建立RTMP连接
*/
int RTMP_Connect(RTMP *r, RTMPPacket *cp, struct sockaddr *dst)
{
// socket结构体
struct sockaddr_in service;
if (!r->Link.hostname.av_len)
return FALSE;
// 设置socket地址
memset(&service, 0, sizeof(struct sockaddr_in));
service.sin_family = AF_INET;
if (r->Link.socksport)
{
/* Connect via SOCKS,使用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;
}
if (r->m_nstub == 1)
{
// 设置socket的ip地址和端口号
rtmp_sockaddr_set_ip((struct sockaddr*)&service, inet_addr(r->m_stubip));
service.sin_port = htons(r->m_stubport);
}
// 第0次连接,主要用于建立socket连接,并未开始真正的建立RTMP连接
// 设置socket的超时和socket接收、发送缓冲区的大小
if (!RTMP_Connect0(r, (struct sockaddr *)&service))
return FALSE;
if (dst)
memcpy(dst, &service, sizeof(struct sockaddr));
r->m_bSendCounter = TRUE;
// 第1次连接,真正建立RTMP连接,主要包括握手和发送connect命令
return RTMP_Connect1(r, cp);
}
RTMP_Connect()函数主要调用了上述红色标记出来的四个函数来完成RTMP网络连接,接下来我们分别来分析这四个函数。
/**
* @brief 将主机的ip地址和端口号添加到socket地址结构中
*
* @param service : socket的IPv4地址结构
* @param host : 结构体存有主机名及其长度
* @param port : 待添加的端口号
*
* @return 成功返回TRUE,否则返回FALSE.
*/
static int add_addr_info(struct sockaddr_in *service, AVal *host, int port)
{
char *hostname;
int ret = TRUE;
// 获取主机名
if (host->av_val[host->av_len])
{
// 主机名之后的值非零,为主机名重新申请空间,最后一位补上'\0'
hostname = malloc(host->av_len + 1);
memcpy(hostname, host->av_val, host->av_len);
hostname[host->av_len] = '\0';
}
else
{
// 主机名之后的值为0,直接将主机名地址赋值即可
hostname = host->av_val;
}
// 将主机名转化为32位网络字节序IP地址
service->sin_addr.s_addr = inet_addr(hostname);
// 如果得到的地址值是非法的,则另谋其道
if (service->sin_addr.s_addr == INADDR_NONE)
{
// 根据主机名,获取与主机有关的信息,包括主机别名、地址类型以及该主机的所有IP地址
struct hostent *host = gethostbyname(hostname);
if (host == NULL || host->h_addr == NULL)
{
RTMP_Log(RTMP_LOGERROR, "Problem accessing the DNS. (addr: %s)", hostname);
ret = FALSE;
goto finish;
}
// 设置IP地址
service->sin_addr = *(struct in_addr *)host->h_addr;
}
// 设置端口号
service->sin_port = htons(port);
finish:
// hostname重新申请了空间,则需要释放
if (hostname != host->av_val)
free(hostname);
return ret;
}
/**
* @brief 建立socket连接. r创建一个socket连接到service指定的网络地址。
* 设置socket接收数据的超时时限以及socket接收和发送缓冲区的大小。
*/
int RTMP_Connect0(RTMP *r, struct sockaddr *service)
{
int recv_size = 128*1024;
int send_size = 128*1024;
int len = sizeof(int);
int on = 1;
r->m_sb.sb_timedout = FALSE;
r->m_pausing = 0;
r->m_fDuration = 0.0;
// 创建一个流式socket
r->m_sb.sb_socket = r->m_sock.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (r->m_sb.sb_socket != -1)
{
// 将刚刚创建的socket连接至service指定的网络地址
if (r->m_sock.connect(r->m_sb.sb_socket, service, sizeof(struct sockaddr)) < 0)
{
int err = r->m_sock.getsockerr();
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
// 创建socket失败
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to create socket. Error: %d", __FUNCTION__, r->m_sock.getsockerr());
return FALSE;
}
/* set timeout,设置超时 */
if (r->m_sock.setsockopt)
{
// 获取设定的超时时限,单位毫秒
SET_RCVTIMEO(tv, r->Link.timeout);
// 设置socket接收数据的超时时限,单位毫秒
if (r->m_sock.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);
}
// 设置socket接收和发送缓冲区的大小
r->m_sock.setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_RCVBUF, (char*)&recv_size, len);
r->m_sock.setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_SNDBUF, (char*)&send_size, len);
}
return TRUE;
}
/**
* @brief 建立RTMP连接,从握手开始.
*/
int RTMP_Connect1(RTMP *r, RTMPPacket *cp)
{
if (r->Link.protocol & RTMP_FEATURE_SSL)
{
RTMP_Log(RTMP_LOGERROR, "%s, no SSL/TLS support", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
// 使用Http
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__);
if (!HandShake(r, TRUE))
// 开始握手
{
RTMP_Log(RTMP_LOGERROR, "%s, handshake failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
RTMP_Log(RTMP_LOGDEBUG, "%s, handshaked", __FUNCTION__);
// 发送含有connect命令的数据报,用于建立RTMP连接
if (!SendConnectPacket(r, cp))
{
RTMP_Log(RTMP_LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
return TRUE;
}
第一次连接RTMP_Connect1( )函数主要做了两件事情:
1) HandShake()完成握手,之前已经分析过了;
2) SendConnectPacket()发送connect命令,用于建立RTMP连接。接下来就具体分析一下这个函数。
/**
* @brief 发送connect命令. 这是每次程序运行的时候发送的第一个命令消息.
* 命令消息由命令名,传输ID,和命令对象组成.
* 命令对象由一系列的相关参数组成.
* 可参考rtmp协议:rtmp命令消息--4.1.1节
*/
static int SendConnectPacket(RTMP *r, RTMPPacket *cp)
{
RTMPPacket packet;
char pbuf[4096], *pend = pbuf + sizeof(pbuf);
char *enc;
if (cp)
return RTMP_SendPacket(r, cp, TRUE);
packet.m_nChannel = 0x03;
/* control channel (invoke), 块流ID */
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
// 块消息头类型,长度为11字节
packet.m_packetType = RTMP_PACKET_TYPE_INVOKE;
// 消息类型ID为20,表示该消息用AMF0编码,
// 参考rtmp协议:rtmp命令消息 --- 3.1节
packet.m_nTimeStamp = 0;
// 时间戳
packet.m_nInfoField2 = 0;
// 消息流id
packet.m_hasAbsTimestamp = 0;
// 相对时间
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
enc = packet.m_body;
enc = AMF_EncodeString(enc, pend, &av_connect);
// 将"connnect"字符串采用AMF0编码
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
// 编码命令消息的数目,也就是传输ID
*enc++ = AMF_OBJECT;
// 接下来是AMF对象,内含多个属性编码
// 编码客户端要连接到的服务应用名
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)
{
// 编码flash播放器版本
enc = AMF_EncodeNamedString(enc, pend, &av_flashVer, &r->Link.flashVer);
if (!enc)
return FALSE;
}
if (r->Link.swfUrl.av_len)
{
// 编码发起连接的swf文件的url
enc = AMF_EncodeNamedString(enc, pend, &av_swfUrl, &r->Link.swfUrl);
if (!enc)
return FALSE;
}
if (r->Link.tcUrl.av_len)
{
// 编码服务url,有下列的格式 protocol://servername:port/appName/appInstance
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)
{
// 编码SWF文件被加载的页面的Url
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, AMF编码方法 */
enc = AMF_EncodeNamedNumber(enc, pend, &av_objectEncoding, r->m_fEncoding);
if (!enc)
return FALSE;
}
if (enc + 3 >= pend)
return FALSE;
// AMF对象结束标志
*enc++ = 0;
*enc++ = 0;
/* end of object - 0x00 0x00 0x09 */
*enc++ = AMF_OBJECT_END;
/* 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);
}