RTMPdump(libRTMP) 源代码分析 5: 建立一个流媒体连接 (NetConnection部分)
=====================================================
RTMPdump(libRTMP) 源代码分析系列文章:
RTMPDump (libRTMP) 源代码分析2:解析RTMP地址——RTMP_ParseURL()
RTMPdump (libRTMP) 源代码分析3: AMF编码
RTMPdump (libRTMP) 源代码分析4: 连接第一步——握手 (HandShake)
RTMPdump (libRTMP) 源代码分析5: 建立一个流媒体连接 (NetConnection部分)
RTMPdump (libRTMP) 源代码分析6: 建立一个流媒体连接 (NetStream部分 1)
RTMPdump (libRTMP) 源代码分析7: 建立一个流媒体连接 (NetStream部分 2)
RTMPdump (libRTMP) 源代码分析8: 发送消息 (Message)
RTMPdump (libRTMP) 源代码分析9: 接收消息 (Message) (接收视音频数据)
RTMPdump (libRTMP) 源代码分析10: 处理各种消息 (Message)
=====================================================
函数调用结构图
RTMPDump (libRTMP)的整体的函数调用结构图如下图所示。
详细分析
本篇文章分析一下RTMPdump里面的建立一个流媒体连接过程中的函数调用。
之前已经简单分析过流媒体链接的建立过程:
而且分析过其函数调用过程:
在这里就不详细叙述了,其实主要是这两个函数:
RTMP_Connect()
RTMP_ConnectStream()
第一个函数用于建立RTMP中的NetConnection,第二个函数用于建立RTMP中的NetStream。一般是先调用第一个函数,然后调用第二个函数。
下面先来看看RTMP_Connect():
注意:贴上去的源代码是修改过的RTMPdump,我添加了输出信息的代码,形如:r->dlg->AppendCInfo("建立连接:第0次连接。开始建立Socket连接");改代码不影响程序运行,可忽略。
RTMP_Connect()
//连接
int
RTMP_Connect(RTMP *r, RTMPPacket *cp)
{
//Socket结构体
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)
{
//加入地址信息
/* 使用SOCKS连接 */
if (!add_addr_info(&service, &r->Link.sockshost, r->Link.socksport))
return FALSE;
}
else
{
/* 直接连接 */
if (!add_addr_info(&service, &r->Link.hostname, r->Link.port))
return FALSE;
}
//-----------------
r->dlg->AppendCInfo("建立连接:第0次连接。开始建立Socket连接");
//-----------------------------
if (!RTMP_Connect0(r, (struct sockaddr *)&service)){
r->dlg->AppendCInfo("建立连接:第0次连接。建立Socket连接失败");
return FALSE;
}
//-----------------
r->dlg->AppendCInfo("建立连接:第0次连接。建立Socket连接成功");
//-----------------------------
r->m_bSendCounter = TRUE;
return RTMP_Connect1(r, cp);
}
我们可以看出调用了两个函数RTMP_Connect0()以及RTMP_Connect1()。按照按先后顺序看看吧:
RTMP_Connect0()
//sockaddr是Linux网络编程的地址结构体一种,其定义如下:
//struct sockaddr{
// unsigned short sa_family;
// char sa_data[14];
//};
//说明:sa_family:是地址家族,也称作,协议族,一般都是“AF_xxx”的形式。通常大多用的是都是AF_INET。
// sa_data:是14字节协议地址。
//有时不使用sockaddr,而使用sockaddr_in(多用在windows)(等价)
//struct sockaddr_in {
// short int sin_family; /* Address family */
// unsigned short int sin_port; /* Port number */
// struct in_addr sin_addr; /* Internet address */
// unsigned char sin_zero[8]; /* Same size as struct sockaddr */
//};
//union {
// struct{
// unsigned char s_b1,s_b2,s_b3,s_b4;
// } S_un_b;
// struct {
// unsigned short s_w1,s_w2;
// } S_un_w;
// unsigned long S_addr;
// } S_un;
//} in_addr;
//第0次连接,建立Socket连接
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;
//创建一个Socket,并把Socket序号赋值给相应变量
//-----------------
r->dlg->AppendCInfo("建立连接:第0次连接。create一个Socket");
//-----------------------------
r->m_sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (r->m_sb.sb_socket != -1)
{
//定义函数 int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
//函数说明 connect()用来将参数sockfd 的Socket(刚刚创建)连至参数serv_addr
//指定的网络地址。参数addrlen为sockaddr的结构长度。
//连接
RTMP_LogPrintf("建立Socket连接!\n");
//-----------------
r->dlg->AppendCInfo("建立连接:第0次连接。connect该Socket");
//-----------------------------
if (connect(r->m_sb.sb_socket, service, sizeof(struct sockaddr)) < 0)
{
//-----------------
r->dlg->AppendCInfo("建立连接:第0次连接。connect该Socket失败");
//-----------------------------
int err = GetSockError();
RTMP_Log(RTMP_LOGERROR, "%s, failed to connect socket. %d (%s)",
__FUNCTION__, err, strerror(err));
RTMP_Close(r);
return FALSE;
}
//-----------------
r->dlg->AppendCInfo("建立连接:第0次连接。connect该Socket成功");
//-----------------------------
//指定了端口号。注:这不是必需的。
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;
}
可见RTMP_Connect0()主要用于建立Socket连接,并未开始真正的建立RTMP连接。
再来看看RTMP_Connect1(),这是真正建立RTMP连接的函数:
RTMP_Connect1()
//第1次连接,从握手开始
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((SSL *)r->m_sb.sb_ssl, r->m_sb.sb_socket);
if (TLS_connect((SSL *)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
}
//使用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);
HTTP_read(r, 1);
r->m_msgCounter = 0;
}
RTMP_Log(RTMP_LOGDEBUG, "%s, ... connected, handshaking", __FUNCTION__);
//握手----------------
r->dlg->AppendCInfo("建立连接:第1次连接。开始握手(HandShake)");
//-----------------------------
RTMP_LogPrintf("开始握手(HandShake)!\n");
if (!HandShake(r, TRUE))
{
//----------------
r->dlg->AppendCInfo("建立连接:第1次连接。握手(HandShake)失败!");
//-----------------------------
RTMP_Log(RTMP_LOGERROR, "%s, handshake failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
//----------------
r->dlg->AppendCInfo("建立连接:第1次连接。握手(HandShake)成功");
//-----------------------------
RTMP_LogPrintf("握手(HandShake)完毕!\n");
RTMP_Log(RTMP_LOGDEBUG, "%s, handshaked", __FUNCTION__);
//发送“connect”命令--------------
//----------------
r->dlg->AppendCInfo("建立连接:第1次连接。开始建立网络连接(NetConnection)");
//-----------------------------
RTMP_LogPrintf("开始建立网络连接(NetConnection)!\n");
//----------------
r->dlg->AppendCInfo("发送数据。消息 命令 (typeID=20) (Connect)。");
//-----------------------------
if (!SendConnectPacket(r, cp))
{
//----------------
r->dlg->AppendCInfo("建立连接:第1次连接。建立网络连接(NetConnection)失败!");
//-----------------------------
RTMP_Log(RTMP_LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
//----------------
r->dlg->AppendCInfo("建立连接:第1次连接。建立网络连接(NetConnection)成功");
//-----------------------------
RTMP_LogPrintf("命令消息“Connect”发送完毕!\n");
return TRUE;
}
该函数做了以下事情:
HandShake()完成握手,之前已经分析过:RTMPdump 源代码分析 4: 连接第一步——握手(Hand Shake)
SendConnectPacket()发送包含“connect”命令的数据报,用于开始建立RTMP连接。具体该函数是怎么调用的,以后有机会再进行分析。
至此RTMP_Connect()分析完毕。
rtmpdump源代码(Linux):http://download.csdn.net/detail/leixiaohua1020/6376561
rtmpdump源代码(VC 2005 工程):http://download.csdn.net/detail/leixiaohua1020/6563163
RTMPdump(libRTMP) 源代码分析 5: 建立一个流媒体连接 (NetConnection部分)的更多相关文章
- RTMPdump(libRTMP) 源代码分析 7: 建立一个流媒体连接 (NetStream部分 2)
===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...
- RTMPdump(libRTMP) 源代码分析 6: 建立一个流媒体连接 (NetStream部分 1)
===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...
- Spark源代码分析之中的一个:Job提交执行总流程概述
Spark是一个基于内存的分布式计算框架.执行在其上的应用程序,依照Action被划分为一个个Job.而Job提交执行的总流程.大致分为两个阶段: 1.Stage划分与提交 (1)Job依照RDD之间 ...
- RTMPdump(libRTMP)源代码分析 4: 连接第一步——握手(Hand Shake)
===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...
- RTMPdump(libRTMP) 源代码分析 10: 处理各种消息(Message)
===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...
- RTMPdump(libRTMP) 源代码分析 9: 接收消息(Message)(接收视音频数据)
===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...
- RTMPdump(libRTMP) 源代码分析 8: 发送消息(Message)
===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...
- 转:RTMPDump源代码分析
0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...
- Android在如何建立一个WebServer
今天老板交待任务最终完成了,感觉收获颇多,所以写一个关于它的记录,首先,看一下.老板的需求 需求: 希望移动端的用户标识(IMEI)和HTML页面的用户标识(Cookie)连接起来,当中HTML页面可 ...
随机推荐
- memcached实战系列(七)理解Memcached的数据过期方式、新建过程、查找过程
1.1.1. 新建Item分配内存过程 1:快速定位slab classid,先计算Item长度 key键长+flag+suffix(16字节)+value值长+结构大小(32字节),如90byte ...
- Dynamics CRM 2015Online Update1 new feature之 插件跟踪日志
在最新的CRM2015Online Update1版本中加入了一个新功能-插件跟踪日志,与其说是新功能更应该说是对原有功能的加强,因为ITracingService这个接口在2013中已经引入了, ...
- Android的GridView的用法-android学习之旅(二十七)
Gridview简介 GridView和ListView有相同的父类AbsListView.他和ListView唯一的区别是Gridview可以显示多列,如果不设置列数,就默认显示一列,变成了List ...
- JUI/DWZ介绍、简单使用
简介 由于开发的项目使用JUI,所以学习了. DWZ富客户端框架(jQuery RIAframework), 是中国人自己开发的基于jQuery实现的Ajax RIA开源框架.(现在更名为JUI) 可 ...
- Java之泛型编程
1.概念 泛型就是参数化类型.泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率. 2.案例 1)先看下面案例: //不适用泛型编程 Apple app0=ne ...
- Java-IO之对象输入流输出流(ObjectInputStream和ObjectOutputStream)
ObjectInputStream和ObjectOutputStream的作用是对基本数据和对象进行序列化操作支持.创建文件输出流对应的ObjectOutputStream对象,该ObjectOutp ...
- 《java入门第一季》之泛型引入
泛型的引入: 首先看一段代码体会自动报错. // 看下面这个代码 自动报错 String[] strArray = new String[3]; strArray[0] = "hello&q ...
- 从极大似然函数到EM算法
最近看斯坦福大学的机器学习课程,空下来总结一下参数估计相关的算法知识. 一.极大似然估计: 大学概率论课程都有讲到参数估计的两种基本方法:极大似然估计.矩估计.两种方法都是利用样本信息尽量准确的去描述 ...
- memcached 详解
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.Memcached ...
- java文件的基本操作示例
一.获得控制台用户输入的信息 public String getInputMessage() throws IOException...{ System.out.println("请输入您的 ...