接收H248数据包流程

在看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);

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值