RTMPdump(libRTMP) 源代码分析 7: 建立一个流媒体连接 (NetStream部分 2)
=====================================================
RTMPdump(libRTMP) 源代码分析系列文章:
RTMPdump 源代码分析 1: main()函数
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 源代码分析 6: 建立一个流媒体连接 (NetStream部分 1)
上回说到,有两个函数尤为重要:
RTMP_ReadPacket()
RTMP_ClientPacket()
而且分析了第一个函数。现在我们再来看看第二个函数吧。 第二个函数的主要作用是:处理消息(Message),并做出响应。
先把带注释的代码贴上:
- //处理接收到的Chunk
- int
- RTMP_ClientPacket(RTMP *r, RTMPPacket *packet)
- {
- int bHasMediaPacket = 0;
- switch (packet->m_packetType)
- {
- //RTMP消息类型ID=1,设置块大小
- case 0x01:
- /* chunk size */
- //----------------
- r->dlg->AppendCInfo("处理收到的数据。消息 Set Chunk Size (typeID=1)。");
- //-----------------------------
- RTMP_LogPrintf("处理消息 Set Chunk Size (typeID=1)\n");
- HandleChangeChunkSize(r, packet);
- break;
- //RTMP消息类型ID=3,致谢
- case 0x03:
- /* bytes read report */
- RTMP_Log(RTMP_LOGDEBUG, "%s, received: bytes read report", __FUNCTION__);
- break;
- //RTMP消息类型ID=4,用户控制
- case 0x04:
- /* ctrl */
- //----------------
- r->dlg->AppendCInfo("处理收到的数据。消息 User Control (typeID=4)。");
- //-----------------------------
- RTMP_LogPrintf("处理消息 User Control (typeID=4)\n");
- HandleCtrl(r, packet);
- break;
- //RTMP消息类型ID=5
- case 0x05:
- /* server bw */
- //----------------
- r->dlg->AppendCInfo("处理收到的数据。消息 Window Acknowledgement Size (typeID=5)。");
- //-----------------------------
- RTMP_LogPrintf("处理消息 Window Acknowledgement Size (typeID=5)\n");
- HandleServerBW(r, packet);
- break;
- //RTMP消息类型ID=6
- case 0x06:
- /* client bw */
- //----------------
- r->dlg->AppendCInfo("处理收到的数据。消息 Set Peer Bandwidth (typeID=6)。");
- //-----------------------------
- RTMP_LogPrintf("处理消息 Set Peer Bandwidth (typeID=6)\n");
- HandleClientBW(r, packet);
- break;
- //RTMP消息类型ID=8,音频数据
- case 0x08:
- /* audio data */
- /*RTMP_Log(RTMP_LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize); */
- HandleAudio(r, packet);
- bHasMediaPacket = 1;
- if (!r->m_mediaChannel)
- r->m_mediaChannel = packet->m_nChannel;
- if (!r->m_pausing)
- r->m_mediaStamp = packet->m_nTimeStamp;
- break;
- //RTMP消息类型ID=9,视频数据
- case 0x09:
- /* video data */
- /*RTMP_Log(RTMP_LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize); */
- HandleVideo(r, packet);
- bHasMediaPacket = 1;
- if (!r->m_mediaChannel)
- r->m_mediaChannel = packet->m_nChannel;
- if (!r->m_pausing)
- r->m_mediaStamp = packet->m_nTimeStamp;
- break;
- //RTMP消息类型ID=15,AMF3编码,忽略
- case 0x0F: /* flex stream send */
- RTMP_Log(RTMP_LOGDEBUG,
- "%s, flex stream send, size %lu bytes, not supported, ignoring",
- __FUNCTION__, packet->m_nBodySize);
- break;
- //RTMP消息类型ID=16,AMF3编码,忽略
- case 0x10: /* flex shared object */
- RTMP_Log(RTMP_LOGDEBUG,
- "%s, flex shared object, size %lu bytes, not supported, ignoring",
- __FUNCTION__, packet->m_nBodySize);
- break;
- //RTMP消息类型ID=17,AMF3编码,忽略
- case 0x11: /* flex message */
- {
- RTMP_Log(RTMP_LOGDEBUG,
- "%s, flex message, size %lu bytes, not fully supported",
- __FUNCTION__, packet->m_nBodySize);
- /*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */
- /* some DEBUG code */
- #if 0
- RTMP_LIB_AMFObject obj;
- int nRes = obj.Decode(packet.m_body+1, packet.m_nBodySize-1);
- if(nRes < 0) {
- RTMP_Log(RTMP_LOGERROR, "%s, error decoding AMF3 packet", __FUNCTION__);
- /*return; */
- }
- obj.Dump();
- #endif
- if (HandleInvoke(r, packet->m_body + 1, packet->m_nBodySize - 1) == 1)
- bHasMediaPacket = 2;
- break;
- }
- //RTMP消息类型ID=18,AMF0编码,数据消息
- case 0x12:
- /* metadata (notify) */
- RTMP_Log(RTMP_LOGDEBUG, "%s, received: notify %lu bytes", __FUNCTION__,
- packet->m_nBodySize);
- //处理元数据,暂时注释
- /*
- if (HandleMetadata(r, packet->m_body, packet->m_nBodySize))
- bHasMediaPacket = 1;
- break;
- */
- //RTMP消息类型ID=19,AMF0编码,忽略
- case 0x13:
- RTMP_Log(RTMP_LOGDEBUG, "%s, shared object, not supported, ignoring",
- __FUNCTION__);
- break;
- //RTMP消息类型ID=20,AMF0编码,命令消息
- //处理命令消息!
- case 0x14:
- //----------------
- r->dlg->AppendCInfo("处理收到的数据。消息 命令 (AMF0编码) (typeID=20)。");
- //-----------------------------
- /* invoke */
- RTMP_Log(RTMP_LOGDEBUG, "%s, received: invoke %lu bytes", __FUNCTION__,
- packet->m_nBodySize);
- RTMP_LogPrintf("处理命令消息 (typeID=20,AMF0编码)\n");
- /*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */
- if (HandleInvoke(r, packet->m_body, packet->m_nBodySize) == 1)
- bHasMediaPacket = 2;
- break;
- //RTMP消息类型ID=22
- case 0x16:
- {
- /* go through FLV packets and handle metadata packets */
- unsigned int pos = 0;
- uint32_t nTimeStamp = packet->m_nTimeStamp;
- while (pos + 11 < packet->m_nBodySize)
- {
- uint32_t dataSize = AMF_DecodeInt24(packet->m_body + pos + 1); /* size without header (11) and prevTagSize (4) */
- if (pos + 11 + dataSize + 4 > packet->m_nBodySize)
- {
- RTMP_Log(RTMP_LOGWARNING, "Stream corrupt?!");
- break;
- }
- if (packet->m_body[pos] == 0x12)
- {
- HandleMetadata(r, packet->m_body + pos + 11, dataSize);
- }
- else if (packet->m_body[pos] == 8 || packet->m_body[pos] == 9)
- {
- nTimeStamp = AMF_DecodeInt24(packet->m_body + pos + 4);
- nTimeStamp |= (packet->m_body[pos + 7] << 24);
- }
- pos += (11 + dataSize + 4);
- }
- if (!r->m_pausing)
- r->m_mediaStamp = nTimeStamp;
- /* FLV tag(s) */
- /*RTMP_Log(RTMP_LOGDEBUG, "%s, received: FLV tag(s) %lu bytes", __FUNCTION__, packet.m_nBodySize); */
- bHasMediaPacket = 1;
- break;
- }
- default:
- RTMP_Log(RTMP_LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__,
- packet->m_packetType);
- #ifdef _DEBUG
- RTMP_LogHex(RTMP_LOGDEBUG, (const uint8_t *)packet->m_body, packet->m_nBodySize);
- #endif
- }
- return bHasMediaPacket;
- }
里面注释的比较多,可以看出,大体的思路是,根据接收到的消息(Message)类型的不同,做出不同的响应。例如收到的消息类型为0x01,那么就是设置块(Chunk)大小的协议,那么就调用相应的函数进行处理。
因此,本函数可以说是程序的灵魂,收到的各种命令消息都要经过本函数的判断决定调用哪个函数进行相应的处理。
在这里注意一下消息类型为0x14的消息,即消息类型ID为20的消息,是AMF0编码的命令消息。这在RTMP连接中是非常常见的,比如说各种控制命令:播放,暂停,停止等等。我们来仔细看看它的调用。
可以发现它调用了HandleInvoke()函数来处理服务器发来的AMF0编码的命令,来看看细节:
- /* Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' */
- static int
- HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
- {
- AMFObject obj;
- AVal method;
- int txn;
- int ret = 0, nRes;
- if (body[0] != 0x02) /* make sure it is a string method name we start with */
- {
- RTMP_Log(RTMP_LOGWARNING, "%s, Sanity failed. no string method in invoke packet",
- __FUNCTION__);
- return 0;
- }
- nRes = AMF_Decode(&obj, body, nBodySize, FALSE);
- if (nRes < 0)
- {
- RTMP_Log(RTMP_LOGERROR, "%s, error decoding invoke packet", __FUNCTION__);
- return 0;
- }
- AMF_Dump(&obj);
- AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method);
- txn = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 1));
- RTMP_Log(RTMP_LOGDEBUG, "%s, server invoking <%s>", __FUNCTION__, method.av_val);
- if (AVMATCH(&method, &av__result))
- {
- AVal methodInvoked = {0};
- int i;
- for (i=0; i<r->m_numCalls; i++) {
- if (r->m_methodCalls[i].num == txn) {
- methodInvoked = r->m_methodCalls[i].name;
- AV_erase(r->m_methodCalls, &r->m_numCalls, i, FALSE);
- break;
- }
- }
- if (!methodInvoked.av_val) {
- RTMP_Log(RTMP_LOGDEBUG, "%s, received result id %d without matching request",
- __FUNCTION__, txn);
- goto leave;
- }
- //----------------
- char temp_str[100];
- sprintf(temp_str,"接收数据。消息 %s 的 Result",methodInvoked.av_val);
- r->dlg->AppendCInfo(temp_str);
- //-----------------------------
- RTMP_Log(RTMP_LOGDEBUG, "%s, received result for method call <%s>", __FUNCTION__,
- methodInvoked.av_val);
- if (AVMATCH(&methodInvoked, &av_connect))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","Result (Connect)");
- //-----------------------------
- if (r->Link.token.av_len)
- {
- AMFObjectProperty p;
- if (RTMP_FindFirstMatchingProperty(&obj, &av_secureToken, &p))
- {
- DecodeTEA(&r->Link.token, &p.p_vu.p_aval);
- SendSecureTokenResponse(r, &p.p_vu.p_aval);
- }
- }
- if (r->Link.protocol & RTMP_FEATURE_WRITE)
- {
- SendReleaseStream(r);
- SendFCPublish(r);
- }
- else
- {
- //----------------
- r->dlg->AppendCInfo("发送数据。消息 Window Acknowledgement Size (typeID=5)。");
- //-----------------------------
- RTMP_LogPrintf("发送消息Window Acknowledgement Size(typeID=5)\n");
- RTMP_SendServerBW(r);
- RTMP_SendCtrl(r, 3, 0, 300);
- }
- //----------------
- r->dlg->AppendCInfo("发送数据。消息 命令 (typeID=20) (CreateStream)。");
- //-----------------------------
- RTMP_LogPrintf("发送命令消息“CreateStream” (typeID=20)\n");
- RTMP_SendCreateStream(r);
- if (!(r->Link.protocol & RTMP_FEATURE_WRITE))
- {
- /* Send the FCSubscribe if live stream or if subscribepath is set */
- if (r->Link.subscribepath.av_len)
- SendFCSubscribe(r, &r->Link.subscribepath);
- else if (r->Link.lFlags & RTMP_LF_LIVE)
- SendFCSubscribe(r, &r->Link.playpath);
- }
- }
- else if (AVMATCH(&methodInvoked, &av_createStream))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","Result (CreateStream)");
- //-----------------------------
- r->m_stream_id = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3));
- if (r->Link.protocol & RTMP_FEATURE_WRITE)
- {
- SendPublish(r);
- }
- else
- {
- if (r->Link.lFlags & RTMP_LF_PLST)
- SendPlaylist(r);
- //----------------
- r->dlg->AppendCInfo("发送数据。消息 命令 (typeID=20) (Play)。");
- //-----------------------------
- RTMP_LogPrintf("发送命令消息“play” (typeID=20)\n");
- SendPlay(r);
- RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS);
- }
- }
- else if (AVMATCH(&methodInvoked, &av_play) ||
- AVMATCH(&methodInvoked, &av_publish))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","Result (Play or Publish)");
- //-----------------------------
- r->m_bPlaying = TRUE;
- }
- free(methodInvoked.av_val);
- }
- else if (AVMATCH(&method, &av_onBWDone))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","onBWDone");
- //-----------------------------
- if (!r->m_nBWCheckCounter)
- SendCheckBW(r);
- }
- else if (AVMATCH(&method, &av_onFCSubscribe))
- {
- /* SendOnFCSubscribe(); */
- }
- else if (AVMATCH(&method, &av_onFCUnsubscribe))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","onFCUnsubscribe");
- //-----------------------------
- RTMP_Close(r);
- ret = 1;
- }
- else if (AVMATCH(&method, &av_ping))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","Ping");
- //-----------------------------
- SendPong(r, txn);
- }
- else if (AVMATCH(&method, &av__onbwcheck))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","onBWcheck");
- //-----------------------------
- SendCheckBWResult(r, txn);
- }
- else if (AVMATCH(&method, &av__onbwdone))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","onBWdone");
- //-----------------------------
- int i;
- for (i = 0; i < r->m_numCalls; i++)
- if (AVMATCH(&r->m_methodCalls[i].name, &av__checkbw))
- {
- AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
- break;
- }
- }
- else if (AVMATCH(&method, &av__error))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","error");
- //-----------------------------
- RTMP_Log(RTMP_LOGERROR, "rtmp server sent error");
- }
- else if (AVMATCH(&method, &av_close))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","close");
- //-----------------------------
- RTMP_Log(RTMP_LOGERROR, "rtmp server requested close");
- RTMP_Close(r);
- }
- else if (AVMATCH(&method, &av_onStatus))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","onStatus");
- //-----------------------------
- AMFObject obj2;
- AVal code, level;
- AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2);
- AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code);
- AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level);
- RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val);
- if (AVMATCH(&code, &av_NetStream_Failed)
- || AVMATCH(&code, &av_NetStream_Play_Failed)
- || AVMATCH(&code, &av_NetStream_Play_StreamNotFound)
- || AVMATCH(&code, &av_NetConnection_Connect_InvalidApp))
- {
- r->m_stream_id = -1;
- RTMP_Close(r);
- RTMP_Log(RTMP_LOGERROR, "Closing connection: %s", code.av_val);
- }
- else if (AVMATCH(&code, &av_NetStream_Play_Start))
- {
- int i;
- r->m_bPlaying = TRUE;
- for (i = 0; i < r->m_numCalls; i++)
- {
- if (AVMATCH(&r->m_methodCalls[i].name, &av_play))
- {
- AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
- break;
- }
- }
- }
- else if (AVMATCH(&code, &av_NetStream_Publish_Start))
- {
- int i;
- r->m_bPlaying = TRUE;
- for (i = 0; i < r->m_numCalls; i++)
- {
- if (AVMATCH(&r->m_methodCalls[i].name, &av_publish))
- {
- AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
- break;
- }
- }
- }
- /* Return 1 if this is a Play.Complete or Play.Stop */
- else if (AVMATCH(&code, &av_NetStream_Play_Complete)
- || AVMATCH(&code, &av_NetStream_Play_Stop)
- || AVMATCH(&code, &av_NetStream_Play_UnpublishNotify))
- {
- RTMP_Close(r);
- ret = 1;
- }
- else if (AVMATCH(&code, &av_NetStream_Seek_Notify))
- {
- r->m_read.flags &= ~RTMP_READ_SEEKING;
- }
- else if (AVMATCH(&code, &av_NetStream_Pause_Notify))
- {
- if (r->m_pausing == 1 || r->m_pausing == 2)
- {
- RTMP_SendPause(r, FALSE, r->m_pauseStamp);
- r->m_pausing = 3;
- }
- }
- }
- else if (AVMATCH(&method, &av_playlist_ready))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","playlist_ready");
- //-----------------------------
- int i;
- for (i = 0; i < r->m_numCalls; i++)
- {
- if (AVMATCH(&r->m_methodCalls[i].name, &av_set_playlist))
- {
- AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
- break;
- }
- }
- }
- else
- {
- }
- leave:
- AMF_Reset(&obj);
- return ret;
- }
- int
- RTMP_FindFirstMatchingProperty(AMFObject *obj, const AVal *name,
- AMFObjectProperty * p)
- {
- int n;
- /* this is a small object search to locate the "duration" property */
- for (n = 0; n < obj->o_num; n++)
- {
- AMFObjectProperty *prop = AMF_GetProp(obj, NULL, n);
- if (AVMATCH(&prop->p_name, name))
- {
- *p = *prop;
- return TRUE;
- }
- if (prop->p_type == AMF_OBJECT)
- {
- if (RTMP_FindFirstMatchingProperty(&prop->p_vu.p_object, name, p))
- return TRUE;
- }
- }
- return FALSE;
- }
该函数主要做了以下几步:
1.调用AMF_Decode()解码AMF命令数据
2.调用AMFProp_GetString()获取具体命令的字符串
3.调用AVMATCH()比较字符串,不同的命令做不同的处理,例如以下几个:
- AVMATCH(&methodInvoked, &av_connect)
- AVMATCH(&methodInvoked, &av_createStream)
- AVMATCH(&methodInvoked, &av_play)
- AVMATCH(&methodInvoked, &av_publish)
- AVMATCH(&method, &av_onBWDone)
等等,不一一例举了
具体的处理过程如下所示。在这里说一个“建立网络流”(createStream)的例子,通常发生在建立网络连接(NetConnection)之后,播放(Play)之前。
- else if (AVMATCH(&methodInvoked, &av_createStream))
- {
- //----------------
- r->dlg->AppendMLInfo(20,0,"命令消息","Result (CreateStream)");
- //-----------------------------
- r->m_stream_id = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3));
- if (r->Link.protocol & RTMP_FEATURE_WRITE)
- {
- SendPublish(r);
- }
- else
- {
- if (r->Link.lFlags & RTMP_LF_PLST)
- SendPlaylist(r);
- //----------------
- r->dlg->AppendCInfo("发送数据。消息 命令 (typeID=20) (Play)。");
- //-----------------------------
- RTMP_LogPrintf("发送命令消息“play” (typeID=20)\n");
- SendPlay(r);
- RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS);
- }
- }
由代码可见,程序先获取了stream_id,然后发送了两个消息(Message),分别是SendPlaylist()和SendPlay(),用于获取播放列表,以及开始播放流媒体数据。
rtmpdump源代码(Linux):http://download.csdn.net/detail/leixiaohua1020/6376561
rtmpdump源代码(VC 2005 工程):http://download.csdn.net/detail/leixiaohua1020/6563163
RTMPdump(libRTMP) 源代码分析 7: 建立一个流媒体连接 (NetStream部分 2)相关推荐
- RTMPdump(libRTMP) 源代码分析 6: 建立一个流媒体连接 (NetStream部分 1)
===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...
- RTMPdump(libRTMP) 源代码分析 5: 建立一个流媒体连接 (NetConnection部分)
===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...
- 主机甲和主机乙之间已建立一个TCP连接,TCP最大段长为1000B。若主机甲的当前拥塞窗口为4000B,在主机甲向主机乙连续发送两个最大段后,成功收到主机乙发送的第一个段的确认段,确认段中通告的接收窗
主机甲和主机乙之间已建立一个TCP连接,TCP最大段长为1000B.若主机甲的当前拥塞窗口为4000B,在主机甲向主机乙连续发送两个最大段后,成功收到主机乙发送的第一个段的确认段,确认段中通告的接收窗 ...
- Android通过WebSocket建立一个长连接(带心跳检测)从服务器端接收消息
最近公司要做一款内部使用的工具类app,方便销售部门打电话(其实就是在后台有好多用户数据,之前销售部门同事拨打电话,需要自己从销售后台查看用户手机号等信息,然后自己拿自己手机拨号,然后打出去.现在想实 ...
- 客户端C和服务器S之间建立一个TCP连接,该连接总是以1KB的最大段长发送TCP段,客户端C有足够的数据要发送。当拥塞窗口为16KB的时候发生超时,如果接下来的4个RTT往返时间内的TCP段的传输是成
客户端C和服务器S之间建立一个TCP连接,该连接总是以1KB的最大段长发送TCP段,客户端C有足够的数据要发送.当拥塞窗口为16KB的时候发生超时,如果接下来的4个RTT往返时间内的TCP段的传输是成 ...
- 建立一个GTalk连接和启动一个IM会话
一个GTalk连接代表着设备和GTalk服务器之间的管道.一个IM会话是消息通道,用于处理所有的即时消息的交通:在一个会话中的所有的即时消息都在这个管道中流动. 你可以创建一些不同的连接和很多的IM会 ...
- LibRTMP源代码分析2:解释RTMP地址
转载自:http://nkwavelet.blog.163.com/blog/static/227756038201412022720924/ 获取RTMP流媒体数据很重要的前提是RTMP的URL的解 ...
- telnetd源代码分析之输入一个字符的四个阶段
下面的debug信息来自与/tmp/telenet.debug文件的一部分. 是客户端按下字母e后发生的四个阶段.这篇主要分析第一阶段和第二阶段. 也就是telrcv函数的主要的功能. td: net ...
- 主机甲和主机乙间已建立一个TCP连接,主机甲向主机乙发送了两个连续的TCP段,分别包含300字节和500字节的有效载荷,第一个段的序列号为200,主机乙正确接收到两个段后,发送给主机甲的确认序列号是?
确认序列号=原始序列号+TCP段的长度,所以第一次的确认序列号为200+300=500,第二次确认序列号为500+500=1000
最新文章
- Jmeter工具中参数化、正则表达式提取器、响应断言的实现
- 黄煦涛教授逝世:获誉华人AI视觉鼻祖、一代宗师,完美家庭楷模
- Oracle 数据库 Database Express Edition 11g Release 2 (11.2) 错误解决集锦(安装方面)
- css 图片换行_前端学习口诀VI:html+css口诀结尾篇,值得收藏!
- adb 多点触碰_无法触及的神话
- 用PyMC3进行贝叶斯统计分析(代码+实例)
- springMVC 源码级别总结原理,DispatcherServlet核心方法
- ASP.NET Web API的Controller是如何被创建的?
- 第十次课:Python函数(一)
- linux 程序的权限设置,Linux权限设置详解
- LonLife-ACM 1129 - 喵哈哈村的战斗魔法师丶坏坏い月
- 看院线电影就用移动电影院V2.0App
- 学委参加博客之星评选啦
- 夜神模拟器抓取数据包
- [DSP 日常记录] #1 冯诺依曼结构、哈佛结构与改进型哈佛结构
- java点击车次显示详情_Web项目专项训练——火车车次信息管理系统代码分享
- initial commit
- 正点原子FreeRTOS(上)
- python自然语言处理学习笔记三
- matlab如何修复照片裂纹,把撕裂破旧老照片修复还原的PS技巧