LibRTMP源代码分析5:建立网络连接

服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。他们的关系如图所示:

LibRTMP源代码分析5:建立网络连接(NetConnection) - nkwavelet - 小波的世界

  网络连接的基本步骤在第一篇文章中有所介绍,此处不再重复。源代码中的 RTMP_Connect(...) 用于建立RTMP网络连接。

/**
 * @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 设置scoket的ip地址
 *
 * @param a  : 待设置的scoket指针
 * @param ip : 32位的网络字节序IP地址
 */
void rtmp_sockaddr_set_ip(struct sockaddr *a, unsigned long ip)
{
union { struct sockaddr_in ain; struct sockaddr addr; } ts;
ts.addr = a[0];
ts.ain.sin_addr.s_addr = ip;
a[0] = ts.addr;
}

/**
 * @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);
}

       发送各种命令消息的过程比较相似,基本上都是先建立一个 RTMPPacket 格式的包packet,然后填充该packet的各个字段。各字段的取值可以参考不同命令消息的文档,其中最复杂的一个过程就是生成packet.m_body数据。一旦packet的封装完成,最后都是调用RTMP_SendPacket()函数来发送这个packet。RTMP_SendPacket()函数代码比较长,会在后续单独的一篇文章中详细分析其代码。
最后用一个图总结函数RTMP_Connect()的调用关系:
LibRTMP源代码分析5:建立网络连接(NetConnection) - nkwavelet - 小波的世界
科技园出了一位股市怪才,炒股2年他只靠1不败铁律 渝和 · 鹓鶵
股票“三不卖七不买“6字黄金口诀,只买井喷股 赢数百万身家! 中资 · 顶新
byxdaz关注
byxdaz

950篇文章

排名:86

wongainia158158关注
wongainia158158

47篇文章

排名:千里之外

huangmindong关注
huangmindong

298篇文章

排名:千里之外

比较清纯关注
比较清纯

255篇文章

排名:千里之外

2月起,科技园有种学历叫免考本科,一年课程,正规本科,学信网可查 爱华学历中心 · 顶新
科技园51岁股民凭8年资深股经探得1定律分析法,赢数百万身家! 纬正实业 · 鹓鶵
股王经典口诀,涨三不追,跌四不压....看完恍然大悟 投石 · 鹓鶵
科技园竟有一位51岁股市奇才,炒股2年靠1个铁律,存款惊呆老婆 渝和 · 鹓鶵
良心记者揭露科技园蜂蜜市场黑幕!亲赴深山,发现惊人真相! 新钥 · 顶新
一个长期喝土蜂蜜的人,竟然变成了这样,再忙也要看一下 顺端 · 鹓鶵

没有更多推荐了,返回首页

猿学习

不良信息举报

LibRTMP源代码分析5:建立网络连接 - wongainia158158的博客

最多只允许输入30个字