发送H248数据包流程

来源:互联网 发布:淘宝客服图片图片发出 编辑:程序博客网 时间:2024/05/17 04:42
这里例举发送注册包为例//mgr为对象管理实例// rvMdmTermMgrGetRootTermination(mgr)为获取ROOT对象//为默认的ServiceChange描述符对象// scReason为RV_MEGACOSERVICECHANGEREASON_COLDBOOT(冷启动)// rvMdmTermMgrProcessRootServiceChangeReply为该事务请求的应答回调函数,当接收到针对该事务的应答时会调用这个函数来处理应答rvMdmTermMgrSendServiceChange(mgr, rvMdmTermMgrGetRootTermination(mgr), scDesc, scReason, rvMdmTermMgrProcessRootServiceChangeReply);//构建一个空的事务对象rvMegacoTransactionConstructA(&transaction, mgr->genAlloc);//构建事务中的动作列表transaction ->actionListaction = rvMegacoTransactionAllocAction(&transaction);//初始化动作// rvMdmContextGetId(rvMdmTermGetContext(term))为获取ROOT节点所在的上下文ID,当前为NULLrvMegacoActionConstructA(action, rvMdmContextGetId(rvMdmTermGetContext(term)), mgr->genAlloc);rvMegacoContextIdConstructCopy(&x->contextId, id, alloc);//将ID复制到上下文对象中rvVectorConstruct(RvMegacoCommand)(&x->commandList, alloc);//构造上下文命令列表rvMegacoContextPropertiesConstructA(&x->contextProperties, alloc);//构造属性x->contextAudit = RV_MEGACOCONTEXTAUDIT_NONE;//关联审计为空//构建动作中的命令列表cmd = rvMegacoActionAllocCommand(action);//传入参数依次为命令对象,终端ID,ServiceChange描述符对象,内存分配对象rvMegacoServiceChangeCommandConstructA(cmd, rvMdmTermGetTermId(term), &sc, mgr->genAlloc);//构建命令,指定终端ID和命令类型rvMegacoCommandConstruct(x, RV_MEGACOCOMMANDTYPE_SVCCHANGE, id, alloc);//给命令对象复制ServiceChange描述符rvMegacoServiceChangeDescriptorConstructCopy(&x->command.serviceChange.sc, sc, alloc);//传入参数依次为远方实例,构建好的事务对象,应答回调函数,对象管理实例//该函数为主发送函数,上面只是构建好要处理的事务rvMegacoEntitySendRequest(mgc, &transaction, onReply, mgr);stack = remote->stack;//获取协议栈对象localEntity = rvMegacoEntityGetLocalEntity(remote);//获取本地实例对象tId = rvMegacoEntityGetNextTransactionId(localEntity);//获取事务ID,并自增加rvMegacoTransactionSetId(request, tId);//给请求事务设置事务ID//构建一个事务控制块对象(TCB)tcbPtr = (RvMegacoTcb *)rvAllocAllocate(stack->tcbAlloc, sizeof(RvMegacoTcb));//初始化事务控制块对象,其中设置了一个控制块定时器并设置了回调函数rvMegacoTcbConstruct(tcbPtr, remote, RV_TRUE, tId, &stack->timerMgr, &stack->tcbLogSource);tcbPtr->localEntity = rvMegacoEntityGetLocalEntity(remoteEntity);tcbPtr->remoteEntity = remoteEntity;tcbPtr->tId = tId;tcbPtr->state = RV_MEGACOTCBSTATE_IDLE;tcbPtr->refs = 0;tcbPtr->logSource = tcbLogSource;tcbPtr->bHistoryTimerExpired = RV_FALSE;rvMegacoTimerConstruct(timerMgr, &tcbPtr->timer, 0, rvMegacoTcbProcessTimer, tcbPtr);rvStrStreamConstruct(&tcbPtr->encodedTransaction, RV_MEGACOTCB_ENCODEBUFINITSIZE, rvMegacoTcbGetStack(tcbPtr)->sendBufAlloc);tcbPtr->callbackData = NULL;tcbPtr->u.requestData.replyCallback = NULL;tcbPtr->u.requestData.pendingReceived = RV_FALSE;tcbPtr->u.requestData.timesSent = 0;//将事务控制块加入本地实例的entity->tcbs表中,主要是用于对于该事务的应答消息的回调处理rvMegacoEntityAddTcb(localEntity, tcbPtr);//把事务对象转化为最终的文本消息,存入控制块的encodedTransaction中rvMegacoTransactionEncode(request, &tcbPtr->encodedTransaction, stack->encodeCompact, &stack->logSource);//设置事务收到应答后的回调处理函数tcbPtr->u.requestData.replyCallback = f;tcbPtr->callbackData = data;//管理对象实例tcbPtr->logSource = &stack->tcbLogSource;tcbPtr->state = RV_MEGACOTCBSTATE_REQUEST_QUEUED;//请求事务并在队列中rvMegacoEntitySendListInsert(remote, tcbPtr);size = rvStrStreamGetSize(&tcb->encodedTransaction);//计算消息大小//如果消息大小为0或者大于事务对象允许的最大值则将该TCB放入栈的badlist中,其中有个事务更新线程会处理badlist列表if(size == 0)或 if(size > remote->u.remote.maxTransactionSize)rvMegacoStackUpdateFailedTcb(stack, tcb);return// rvStrStreamGetSize为msgStream中已经使用了多少字节// rvMegacoEntityGetMaxMsgSize为栈支持的最大字节//总体意思是如果已有消息流字节加上当前消息,大于栈中消息的最大字节数时,则直接将消息发出,这里会大于是因为有延时发送选项,H248中定义一个消息可以包含N个事务一同发送。if(rvStrStreamGetSize(&remote->u.remote.msgStream) + size > rvMegacoEntityGetMaxMsgSize(remote))rvMegacoEntitySendEx(remote, RV_TRUE);//这个函数放在后面单独看//将TCB放入远端实例的事务待发送队列中rvPtrVectorPushBack(&remote->u.remote.transactionsToSend, tcb);//将消息存入remote->u.remote.msgStream中rvStrStreamWriteMem(&remote->u.remote.msgStream, rvStrStreamGetStr(&tcb->encodedTransaction), size);if(stack->sendDelay == 0)//如果没有设置延时发送rvMegacoEntitySendEx(remote, RV_TRUE);else if(!remote->u.remote.timerRunning)//否则设置了延时发送并且定时器未开//开启定时器,具体的定时器超时回调处理函数得看mgstartup主流程中远端实例构建时设置的定时器回调,这里暂不看,默认为没延时rvMegacoTimerReset(rvMegacoEntityGetSendTimer(remote), stack->sendDelay);remote->u.remote.timerRunning = RV_TRUE;这里主要看一下rvMegacoEntitySendEx函数static void rvMegacoEntitySendEx(RvMegacoEntity *remote, RvBool stopTimer)stack = remote->stack;local = rvMegacoEntityGetLocalEntity(remote);transportType = rvMegacoEntityGetTransportType(remote);encodingType = rvMegacoEntityGetEncoding(remote);if (stopTimer)rvMegacoTimerStop(rvMegacoEntityGetSendTimer(remote));//停止延时发送定时器//如果消息流状态错误,或者发送队列和应答队列都为空,则直接返回if ((rvStrStreamGetStatus(&remote->u.remote.msgStream) != RV_OK) || ((rvPtrVectorSize(&remote->u.remote.transactionsToSend) == 0) && (rvMegacoResponseAckIsEmpty(&remote->u.remote.acks))))RETURN;//如果是UDP传输协议,则取出本地实例创建好的SOCKETrvSock = local->socket;if (rvSock != RV_INVALID_SOCKET)//如果SOCKET正常//这里检查远端实例是否有ACK事务,如果有的话加入要发送的消息流中remote->u.remote.msgStream,如果判断加入ack后的消息流总大小已经超过了栈规定的最大大小,则把刚才加入到消息流中的字符删除并返回错误,否则没问题的话则删除远端实例中的ACK事务if(! rvMegacoEntityEncodeResponseAcks(remote))//如果返回失败,则将重新编码标记设置为TRUE,用于后面待消息流发送完后,重新将ACK事务放入消息流中retryAckEncode = RV_TRUE;//在消息流最后添置C字符串结束符rvStrStreamEnds(&remote->u.remote.msgStream);rvStrStreamBufPutC(&x->sbuf,'\0');//参数依次为协议栈对象,文本消息,本地SOCKET描述符,远端地址,传输类型,编码类型 rvMegacoStackSendMessage(stack, &remote->u.remote.msgStream, &rvSock, &remote->socketAddr, transportType, encodingType);//待发消息 char *message = rvStrStreamGetStr(msgStream) + RV_MEGACOENTITY_SENDBUFHDRSIZE;//待发消息长度RvSize_t length = rvStrStreamGetSize(msgStream) - RV_MEGACOENTITY_SENDBUFHDRSIZE - 1;//如果用户注册了原始发送回调则执行if(stack->rawSendCb != NULL)stack->rawSendCb(stack, message, stack->rawSendData, destAddr, rvStrStreamGetCapacity(msgStream) - RV_MEGACOENTITY_SENDBUFHDRSIZE - 1, &length)RvSocketSendBuffer(socket, (RvUint8*)message, length, (RvAddress*)destAddr, rvMegacoLogGet(), NULL);RvSocketSharerShare(logMgr, sock, &ourSocket);//获取共享SOCKET//将RVSocketAddr转化为系统的SocketAdd类型RvSocketAddressToSockAddr(remoteAddress, (RV_SOCKADDR_PTR)sockdata, &socklen);//********这里真正的用系统SOCKET发包**********UDP_SEND(ourSocket, (char *)buffer, (int)bytesToSend, RV_MSG_NOSIGNAL, (RV_SOCKADDR_PTR)sockdata, socklen);//这里向栈对象的goolist或badlist加入处理完的TCB,然后由栈对象的线程函数去这两个队列中更新TCB的状态等,上面的发送如果成功则sendSuccessful=TRUErvMegacoStackUpdateTcbs(stack, &remote->u.remote.transactionsToSend, sendSuccessful);//根据是否发送成功来选择更新列表对象updateList = sendSuccessful ? &stack->goodTcbs : &stack->badTcbs;//将远端实例的所有发送队列加入指定更新队表中rvPtrVectorInsertMult(updateList, rvVectorEnd(updateList), rvVectorBegin(tcbs), rvVectorEnd(tcbs));//触发栈的更新信号量RvSemaphorePost(&stack->tcbUpdateSem, rvMegacoLogGet());//清空远端实例的事务发送队列rvPtrVectorClear(&remote->u.remote.transactionsToSend);//将远端消息流复位rvMegacoEntityResetSendBuffer(remote);//如果上面有ACK事务,但是在消息流中放不下了,则在最后重新将ACK放入消息流中,并删除ACK事务if(retryAckEncode)rvMegacoEntityEncodeResponseAcks(remote);//延时发送已经关闭remote->u.remote.timerRunning = RV_FALSE;(OK,到这里协议包已经通过SOCKET发送出去了,其中还有两件未处理的事情,一件是TCB存入了本地实例的TCBS队列中,用于该事务有应答后执行TCB中的回调处理;还有一件事情就是TCB存入了栈对象的更新列表中,更新线程函数见下面)static void rvMegacoStackProcessTcbUpdates(RvThread *thread, void *data)线程函数//为两个临时变量分配内存rvPtrVectorConstruct(&good, stack->genAlloc);rvPtrVectorConstruct(&bad, stack->genAlloc);for(;;)//当上面数据包被发送后会激活这里RvSemaphoreWait(&stack->tcbUpdateSem, rvMegacoLogGet());//复制出来,将badtcbs和goodtcbs队列释放rvPtrVectorCopy(&good, &stack->goodTcbs);rvPtrVectorCopy(&bad, &stack->badTcbs);rvPtrVectorClear(&stack->goodTcbs);rvPtrVectorClear(&stack->badTcbs);//如果有事务控制块对象if (rvPtrVectorSize(&good) > 0)//遍列所有for(iter=rvPtrVectorBegin(&good); iter!=rvPtrVectorEnd(&good); iter=rvPtrVectorIterNext(iter))rvMegacoTcbUpdateOnSend((RvMegacoTcb *)*rvPtrVectorIterData(iter));switch(tcbPtr->state)//刚发出去的包,正在队列中case RV_MEGACOTCBSTATE_REQUEST_QUEUED:if(++(tcbPtr->u.requestData.timesSent) == 1)//第一次发送//在TCB中记下发送的时间tcbPtr->u.requestData.origSendTime = RvTimestampGet(rvMegacoLogGet());//获取事务重传定时器时间,如果收到pend消息则设置超时时间为最长时间,否则获取第一次事务重传时间timeout = tcbPtr->u.requestData.pendingReceived ? RV_MEGACOTCB_LONG_TRANSACTION_TIMEOUT : (RvUint)(rvMegacoEntityGetFirstRetransTimeout(tcbPtr->remoteEntity) / RV_TIME_NSECPERMSEC);else//否则不是第一次发送,表明已经重传//获取事务重传定时器时间,如果收到pend消息则设置超时时间为最长时间,否则获取下一次重传时间(这里会比上次加长)timeout = tcbPtr->u.requestData.pendingReceived ? RV_MEGACOTCB_LONG_TRANSACTION_TIMEOUT : (RvUint)(rvMegacoEntityGetNextRetransTimeout(tcbPtr->remoteEntity) / RV_TIME_NSECPERMSEC);rvPtrVectorDestruct(&good);rvPtrVectorDestruct(&bad);//切换TCB的状态为请求已经发送tcbPtr->state = RV_MEGACOTCBSTATE_REQUEST_SENT;case //其它的条件我们这里不关心,略if(timeout)//启动那个TCB实例的定时器,超时触发指定回调rvMegacoTcbResetTimer(tcbPtr, timeout);(在这里TCB的更新已经完成,目前那个TCB有两个待处理的事情,一个是它在本地实例的tcbs列表中,用于收到事务应答时回调应答处理函数,一个是TCB自身的定时器已经启动,待超时后触发自身定时器回调,定时器回调我们在下面来看)rvMegacoTcbProcessTimer(RvMegacoTimer *timer, void *data) TCB事务控制块自身的回调,用于重传tcbPtr = (RvMegacoTcb *)data;switch (tcbPtr->state)//当TCB标记为已经发出的请求数据包,遇到事务超时定时器后case RV_MEGACOTCBSTATE_REQUEST_SENT://用当前时间减去最初发包时间,如果小于事务总的超时时间,则进行重传if (RvInt64IsLessThan(RvInt64Sub(RvTimestampGet(rvMegacoLogGet()), tcbPtr->u.requestData.origSendTime), rvMegacoTcbGetStack(tcbPtr)->tMax))//将TCB状态重新标记为发送请求在队列中tcbPtr->state = RV_MEGACOTCBSTATE_REQUEST_QUEUED;//这里主要是将TCB加入到远端实例的发送队列中,并将事务转化为文本消息放入远端实例的消息流对象中,然后最终通过SOCKET发送出去,并将TCB再次加入到协议栈对象的badtcbs或goodtcbs列表中,等待TCB更新线程处理,更新线程激活TCB的定时器,继续等待TCB超时rvMegacoEntitySendListInsert(tcbPtr->remoteEntity, tcbPtr);else//否就该TCB超过了事务的总生命周期//将TCB的状态标记为请求已经结束tcbPtr->state = RV_MEGACOTCBSTATE_REQUEST_COMPLETE;//设置从本地实例中删除的标记,因为在本地实例的tcbs列表中还存有一份用于应答消息的回调deleteFromLocal = RV_TRUE;//如果TCB有应答回调函数,则触发,并告知原因为超时。在这里注册的回调如果见到是超时什么都没有做if(tcbPtr->u.requestData.replyCallback != NULL)tcbPtr->u.requestData.replyCallback(tcbPtr, NULL, tcbPtr->callbackData, RV_MEGACOREQUESTSTATUS_TIMED_OUT_NO_REPLY);//更新统计rvMegacoStackIncrementFailedCount(rvMegacoTcbGetStack(tcbPtr));//我们这里只关于case 请求分支,其它略//如果前面设置了从本地实例中删除TCB则执行后面的函数,传入参数为本地实例,TCB,FALSEdeleteFromLocal && !rvMegacoEntityRemoveTcb(rvMegacoTcbGetLocalEntity(tcbPtr), tcbPtr, RV_FALSE)success = rvMegacoTcbTryToDelete(tcb);deleteTcb = tcbPtr->refs == 0;// 查看引用计数,如果为0才删除if(deleteTcb)rvMegacoTcbDestruct(tcbPtr);//释放TCB对象rvAllocDeallocate(alloc, sizeof(RvMegacoTcb), tcbPtr);//释放TCB指针if (success)//从本地实例的HASH表中删除rvHashEraseKey(RvMegacoTransactionId, RvMegacoTcbPtr)(&entity->tcbs, &tId);//这里作用主要是为了终端实例的销毁,因为终端实例中包含了事务队列,所以在终端实例被删除时,会判断自身TCB列表是否都结束了,如果没有则等待tcbSem信号量,直到最后一个TCB结束,触发该信号量,使得终端实例可以成功释放post = --(entity->tcbCount) == 0;if (success)rvMegacoStackDecrementTransactionCount(entity->stack, entity->isLocal);//统计if (post)RvSemaphorePost(&entity->tcbSem, rvMegacoLogGet());//同上,这里没有标记从远端实例中删除,所以不执行deleteFromRemote && !rvMegacoEntityRemoveTcb(rvMegacoTcbGetRemoteEntity(tcbPtr), tcbPtr, RV_TRUE))

原创粉丝点击