在看mgStartup流程时,H248的数据包接收机制就已经全部构建好了,只是分部在mgStartup流程的几个位置,代码零散,不看完整个mgStartup流程是没办法理解收包机制的。这里主要以关键点来分析对数据包接收处理的流程。
mgStartup
rvMegacoStackConstruct
rvMegacoTransportConstruct
这里创建了一个传输层对象,并设置了一个回调函数rvMegacoStackProcessPackets,这里标记为关键点A
然后创建了一个线程对象,线程函数为rvMegacoTransportProcessPacketQueue,这里该线程只是创建,没有启动,这里标记为关键点B
这里创建了一个SELECT对象,这里标记为关键点C
这里创建了一个线程对象,线程函数为rvMegacoSocketEngineThread,这个线程已经启动,但是直接就阻塞了,一直等待触发它运行的开关变量开启,什么时候打开一会看下面。这里标记为关键点D
rvMegacoSocketEngineConstruct
rvMegacoEntityConstructLocalEx
这里首先使用系统SOCKET的API创建了H248需要的socket句柄,并使用系统函数bind进行端口绑定,然后封装成RVSocket对象,这里定为关键点E
然后构造了一个SocketData对象,这个对象主要是用来存储接收数据包的,同时将刚才构建的RVSocket对象包含进去,将关键点A中的传输层对象包含进去,对于SocketData对象有段很巧妙的代码,因为该对象指针分配完内存后这个指针在函数栈中就丢弃了,那怎么来找到该指针分配的SocketData对象这块内存呢,其实在别的代码中想引用SocketData这块内存是通过这个对象中的RVSocket找到的,就是根据RVSocket对象在SocketData结构中的成员偏移找到SocketData的,这里定为关键点F
然后将RVScoket对象封装为一个RvFd文件描述符,并将该描述符加入到关键点C的SELECT对象RedSet集合中,并给RvFd设置了一个回调函数rvMegacoTransportRecvFromUdp,这里定为关键点G
rvMegacoTransportAddUdpSocketEx
rvMegacoSystemRegisterStack
rvMegacoStackStart
rvMegacoTransportStart
//这里运行关键点B的rvMegacoTransportProcessPacketQueue线程,该线程函数检测关键点A的传输层对象待处理包队列中是否有数据(在哪里会向这个队列中加数据在下面的代码分析中可以看到),如果有的话,则调用关键点A的rvMegacoStackProcessPackets回调进行处理,该函数最终会根据分析出的不同消息类型触发rvMegacoStackProcessParserError、rvMegacoStackProcessAuthHeader、rvMegacoStackProcessRequest等回调
RvThreadStart
这里就是把关键点D的开关变量进行打开,使得rvMegacoSocketEngineThread线程函数继续向下运行,该函数运行后,使用关键点C的SELECT对象调用系统的select函数监听关键点E的socket是否有消息,如果监听到了,则调用关键点G中rvMegacoTransportRecvFromUdp函数,该函数根据关键点E的RVSocket对象使用结构体内成员偏移方法找到关键点F的SocketData对象,然后从SocketData对象中得到关键点A的传输层对象,调用系统收包函数接收UDP包,将收到消息封装为packetData对象,加入到传层对象待处理包队列中
rvMegacoSocketEngineStart
接收到请求消息后,如果本地没有应答,协议栈自动回复Pending消息及最终请求事务超时的处理流程如下:
首先从上面接收数据包流程中,如果收到MGC请求消息则触发rvMegacoStackProcessRequest
//根据事务ID,查找远端实例的TCBS队列中是否有该事务,如果没有则创建一个新的TCB对象,加入远端实例的TCBS队列中,新的TCB对象创建了一个定时器对象并设置了回调函数rvMegacoTcbProcessTimer,后面需要用到这个函数,这里暂标记为关键点A
tcb = rvMegacoEntityCheckOutTcb(remoteEntity, rvMegacoTransactionGetId(request), RV_TRUE);
//进入到请求处理流程函数
rvMegacoTcbOnRecvRequest(tcb, request);
//判断如果TCB的状态为IDLE(新时为该状态)则执行如下
//启动TCB对象的定时器,用于自动回复Pending消息的超时处理,该定时器就是上面关键点A的定时器,超时后会调上那个回调
rvMegacoTcbResetTimer(tcbPtr, RV_MEGACOTCB_AUTOPENDING_TIMEOUT);
//将TCB状态迁为等待应答
tcbPtr->state = RV_MEGACOTCBSTATE_WAITING_FOR_REPLY;
//执行请求的处理响应,我们想了解本地没有应答后会发生什么,所以这里我们就当代码没有执行
localEntity->u.local.processRequest(tcbPtr, request, localEntity->u.local.processRequestData);
//收到新的事务请求时,该标记都记为TRUE,为了是将请求加入到需要应答的处理中
addResponse = RV_TRUE;
//如果上面标记为TRUE,则将该TCB添加到待监视的应答处理中
if(addResponse)
rvMegacoEntityAddResponse(remoteEntity, rvMegacoTcbGetId(tcbPtr));
//首先得到请求事务超时的默认时间
i = entity->stack->tHist / RV_MEGACOENTITY_RESPONSEPROCESSINGINTERVAL + 1;
//判断监视应答队列中是否为空,如果为空则标记后面要启动监视定时器,不为空的话表明该定时器已经触发过了,该定时器触发在内部函数中会判断如果这个队列不空则一直自激活,只有队列空才停止激活,等待下一个请求过来触发。
startTimer = rvDequeSize(&entity->u.remote.responses) == 0;
//将参数传入的事务ID放入特监视应答队列中
rvDequePushBack(RvMegacoTransactionId)(&entity->u.remote.responses, &transId);
//下面这段代码很经典,其中numToDelete队列就像一个时间轴,该队列与上面//responses队列紧密联系在一起。见下图举例:
//---------------------------------------------------------------
// numToDelete队列 0 0 0 0 0 0 1 0 0 1
// responses队列 A B
//---------------------------------------------------------------
//numToDelete队列有10个成员,成员值为整形数字,responses队列有两个成员,//成员值为事务ID,在numToDelete队列中0表示该时间点没有超时事务,1表示该//时间点responses队列前1个事务已经超时,同理如果是2表明该时间点//responses队列前2个事务已经超时,上图的意思就是事务ID为A的事务会在7
//秒后超时,事务ID为B的事务会在10秒后超时
//如果当前时间轴已经有很长的等待队列,可是用户中途改变了请求事务超时时
//间,并且比当前等待队列的最长时间短,则忽略用户设置,把当前监视事务加到//当前最长的时间点上
if(i + 1 < rvDequeSize(&entity->u.remote.numToDelete))
i = rvDequeSize(&entity->u.remote.numToDelete) - 1;
else
//如果请求事务超时时间比当前时间轴长,则用0来填充时间轴至需要的超时时间
while(i >= rvDequeSize(&entity->u.remote.numToDelete))
rvDequePushBack(RvUint)(&entity->u.remote.numToDelete, &zero);
//步增当前时间点所触发的事务个数
++(*rvDequeAt(&entity->u.remote.numToDelete, i));
//如果前面加了启动定时器标记则启动监视请求事务的定时器,该定时器的回调函//数为rvMegacoEntityProcessResponseTimer,这里记为关键点B
if(startTimer)
rvMegacoTimerReset(&entity->u.remote.responseTimer, RV_MEGACOENTITY_RESPONSEPROCESSINGINTERVAL);
此时已经有两个定时器待触发,协议栈自动回复Pending消息及最终请求事务超时就是分别用这两个定时器实现的,先看一下自动回复Pending消息是怎样实现的。关键点A的定时器超时后执行回调rvMegacoTcbProcessTimer
首先判断当前TCB状态,如果为RV_MEGACOTCBSTATE_WAITING_FOR_REPLY则执行如下:
rvMegacoTcbSendPending(tcbPtr, NULL, NULL);
//停止该定时器
rvMegacoTcbStopTimer(tcbPtr);
//将encodedTransaction清空
rvStrStreamSeekPos(&tcbPtr->encodedTransaction, 0);
//构建要发送的数据包内容,存入encodedTransaction
rvMegacoTransactionPendingEncode(tcbPtr->tId, &tcbPtr->encodedTransaction, rvMegacoTcbGetStack(tcbPtr)->encodeCompact, &remoteEntity->stack->logSource);
//将TCB迁态为Pending消息已经放入队列
tcbPtr->state = RV_MEGACOTCBSTATE_PENDING_QUEUED;
//这里将TCB加入的发送队列并发送,这段代码不分析了,在上次写的发送数据包流程中有这块代码的分析
rvMegacoEntitySendListInsert(remoteEntity, tcbPtr);
最终请求事务超时的实现,关键点B的定时器触发后,调用rvMegacoEntityProcessResponseTimer回调函数
//判断时间轴是否有时间点
if(rvDequeSize(&entity->u.remote.numToDelete))
//如果有时间点,获取顶部时间点的数值
n = rvDequeFront(&entity->u.remote.numToDelete);
//当时间点的数组大于零时,表明该时间点有超时事务发生,用循环是为了处理该时间点如果同时有多个事务超时,则依次处理
while((*n) > 0)
//获取第一个超时事务的ID
id = *rvDequeFront(&entity->u.remote.responses);
//从事务列表中找到指定ID的事务
tcbPtr = rvMegacoEntityCheckOutTcb(entity, id, RV_FALSE);
//这里将超时的TCB的状态变迁为RV_MEGACOTCBSTATE_REPLY_COMPLETE,然后启动关键点A的定时器,超时时间为10ms,超时后触发关键点A的定时器回调函数rvMegacoTcbProcessTimer,该回调函数主要就是将TCB从关键点A中的TCBS队列中删除
rvMegacoTcbOnHistoryTimerExpire(tcbPtr);
//将事务ID从监视事务列表中删除
rvDequePopFront(RvMegacoTransactionId)(&entity->u.remote.responses);
//用于遍历当前时间点的下一个超时事务的触发条件
(*n)--;
//将当前时间点从时间轴上删除
rvDequePopFront(RvUint)(&entity->u.remote.numToDelete);
//如果时间轴上还有时间点,则自触发定时器
if(rvDequeSize(&entity->u.remote.responses))
rvMegacoTimerReset(timer, RV_MEGACOENTITY_RESPONSEPROCESSINGINTERVAL);
接收H248数据包流程
最新推荐文章于 2018-08-18 01:19:03 发布