RTMP_Connect
librtmp用RTMP_Connect这个方法建立网络连接,网络连接代表了服务端应用程序和客户端之间基本的连通关系。 在
RTMP_ParseURL
方法中, 可以通过传入的url解出来host
、port
、application
用解出来的host和port建立socket,然后做一些rtmp握手之类的工作。
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;
RTMP_Log(RTMP_LOGDEBUG, "Link.sockport = %d", r->Link.sockshost);
if (r->Link.socksport) {
/* Connect via SOCKS */
if (!add_addr_info(&service, &r->Link.sockshost, r->Link.socksport))
return FALSE;
} else {
RTMP_Log(RTMP_LOGDEBUG, "hostname: %s, port = %d\n", r->Link.hostname.av_val, r->Link.port);
/* Connect directly */
if (!add_addr_info(&service, &r->Link.hostname, r->Link.port))
return FALSE;
}
// 创建socket, 设置一些参数, 接收数据超时时间,禁用Nagle算法
if (!RTMP_Connect0(r, (struct sockaddr *)&service))
return FALSE;
r->m_bSendCounter = TRUE;
return RTMP_Connect1(r, cp);
}
RTMP_Connect0
RTMP_Connect0
方法中主要就是创建tcp socket, 然后连接服务器, 设置超时, 禁用Nagle算法
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);
if (r->m_sb.sb_socket != -1) {
if (connect(r->m_sb.sb_socket, service, sizeof(struct sockaddr)) < 0) {
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);
}
}
// 禁用Nagle算法, 保证了数据实时性
setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on));
// @remark debug info by http://github.com/ossrs/srs
_srs_state = 2;
return TRUE;
}
RTMP_Connect1
RTMP_Connect1
主要包括握手(HandShake)
,还有向服务器发送连接命令(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
}
RTMP_Log(RTMP_LOGDEBUG, "Link.protocal: %d", r->Link.protocol);
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__);
if (!SendConnectPacket(r, cp)) {
RTMP_Log(RTMP_LOGERROR, "%s, RTMP connect failed.", __FUNCTION__);
RTMP_Close(r);
return FALSE;
}
return TRUE;
}
HandShake
一个RTMP连接以握手开始, RTMP的握手不同以其他协议:RTMP握手由三个固定长度的块组成,而不像其他协议一样带有报头的可变长度的块。客户端和服务端各自发送三块数据,
- 客户端:C0、C1、C2
- 服务端:S0、S1、S2
下面时librtmp中对于握手的处理
static int
HandShake(RTMP *r, int FP9HandShake)
{
int i;
uint32_t uptime, suptime;
int bMatch;
char type;
char clientbuf[RTMP_SIG_SIZE + 1], *clientsig = clientbuf + 1;
char serversig[RTMP_SIG_SIZE];
clientbuf[0] = 0x03; /* not encrypted */
uptime = htonl(RTMP_GetTime());
memcpy(clientsig, &uptime, 4);
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++)
clientsig[i] = (char)(rand() % 256);
#endif
// 发送C0、C1数据块
if (!WriteN(r, clientbuf, RTMP_SIG_SIZE + 1))
return FALSE;
/* 接收S0块 */
if (ReadN(r, &type, 1) != 1) /* 0x03 or 0x06 */
return FALSE;
RTMP_Log(RTMP_LOGDEBUG, "%s: Type Answer : %02X", __FUNCTION__, type);
if (type != clientbuf[0])
RTMP_Log(RTMP_LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d",
__FUNCTION__, clientbuf[0], type);
/* 接收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 发送C2块*/
if (!WriteN(r, serversig, RTMP_SIG_SIZE))
return FALSE;
/* 接收S2块 */
if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
return FALSE;
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
块之后,向客户端发送S0
和S1
块 - 客户端相继接收数据, 直到接收到
S1
数据块之后, 再服务端发送C2
块 - 服务端必须等待接收到
C1
块之后才能发送S2
块 - 客户端必须等待接收到
S2
块才能发送其他数据 - 服务端必须等待接收到
C1
块才能发送其他数据
握手数据块格式
下面介绍一下C0、C1、C2和S0、S1、S2数据块的格式。
C0和S0数据块格式
C0和S0块都是一个单一的八位字节,以一个单独的八位整形域进行处理。
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
| version |
+-+-+-+-+-+-+-+-+
C1和S1的格式
C1和S1的数据包的长度为1536字节, 包括以下字段
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time(4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| zero(4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random bytes |
| (const) |
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- Time(四个字节):这个字段包括了一个timestamp, 用于本终端发送的所有后续块的时间起点。这个值可以时0或者一些任意值。
- Zero(四个字节):这个字段必须时0
- Random data(四个字节):这个字段可以包含任意值
C2和S2的格式
C2和S2数据包长度都是1536字节, 基本上就是S1和C1的副本, 包含以下字段:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time(4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time2(4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random echo |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random echo |
| (const) |
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- Time(四个字节):这个字段必须包含终端在S1(给C2)或者C1(给S2)发的timestamp
- Time2(四个字节):这个字段必须包含终端先前发出数据包(S1或者C1)timestamp
- Random data(四个字节):这个字段必须包含终端发出的S1(给C2)或者S2(给C1)的随机数。
SendConnectPacket
SendConnectPacket
方法的作用是客户端发送connect
命令到服务器端来请求连接到一个服务器应用的实例
connect
命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令的名字,这里是connect |
Transaction ID | 数字 | 总是设置为1 |
Command Object | 对象 | 具有名值对的命令信息对象 |
Optional User Arguments | 对象 | 任意可选对象 |
任意可选对象里面是相当与是一些用户参数, app
、 flashver
、swfUrl
等
下面是librtmp中的连接代码
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) */
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
packet.m_packetType = RTMP_PACKET_TYPE_INVOKE;
packet.m_nTimeStamp = 0;
packet.m_nInfoField2 = 0;
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
enc = packet.m_body;
enc = AMF_EncodeString(enc, pend, &av_connect);
enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
*enc++ = AMF_OBJECT;
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;
/* 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);
}