电脑技术学习

在Vovida的基础上实现自己的SIP协议栈②

dn001

  2.5 HeartLessProxy Run方法的实现
  
  HeartLessProxy::run()
  {
  myWorkerThread->run();
  mySipThread->run();
  }
  通过上面可以看到有两个Run方法的调用,第一个是WorkThread的Run方法,它的主要作用是处理UaBuilder的Process方法,主要用来处理Sptr < Fifo < Sptr < SipProxyEvent > > > myFifo中的各种事件,前面已经具体的介绍了SipProxyEvent类的作用,这个类已经在前面介绍了,其实简单的说,它就是一个本地的各种事件的集合。
  现在我们来看一下两个Run方法的实现:
  2.5.1 WorkerThread的Run方法:
  UaBuilder::process( const Sptr < SipProxyEvent > nextEvent )
  {
  //处理以下的四种事件
  /// SipEvent
  Sptr < SipEvent > sipEvent;
  sipEvent.dynamicCast( nextEvent );
  if ( sipEvent != 0 )
  {
  //处理本地的SIP的事件,包括对状态机的设置和命令/状态队列返回的操作在下面将//对它做具体的介绍
  if( processSipEvent( sipEvent ) )
  {
  return;
  }
  //向消息队列myCallContainer中插入相应的事件信息。
  sendEvent( nextEvent );
  return;
  }
  
  /// UaDeviceEvent
  Sptr < UaDeviceEvent > uaDeviceEvent;
  uaDeviceEvent.dynamicCast( nextEvent );
  if ( uaDeviceEvent != 0 )
  {
  //处理本地的设备事件,最主要的就是处理摘机信号;
  if( processUaDeviceEvent( uaDeviceEvent ) )
  {
  return;
  }
  sendEvent( nextEvent );
  return;
  }
  
  /// UaDigitEvent
  Sptr < UaDigitTimerEvent > uaDigitEvent;
  uaDigitEvent.dynamicCast( nextEvent );
  if ( uaDigitEvent != 0 )
  {
  
  //处理在规定的时间间隔(Kickstart)主动呼叫事件的触发。
  if( processUaDigitEvent( uaDigitEvent ) )
  {
  return;
  }
  sendEvent( nextEvent );
  return;
  }
  
  /// UaTimerEvent
  Sptr < UaTimerEvent > uaTimerEvent;
  uaTimerEvent.dynamicCast( nextEvent );
  if ( uaTimerEvent != 0 )
  {
  //在各种SIP命令的回应产生了超时事件后,系统的事件触发。例如:
  //在StateTrying()中addEntryOperator( new OpStartTimer )在myEntryOperators队列中加入
  //该Operator(指一个操作,例如呼叫或者是进入等待),这里我们这个Operator在时间到达
  //以后户会被OpTimeout::process的方法检测到(isTimeout(event)进行检测,对StateTrying
  //整个状态进行检测,也就是Trying事件),最后假如UaTimerEvent事件被触发,那么,//就会调用:stateMachine->findState( "StateError" )这个状态,进入错误状态,实施错误的
  //处理机制,同时向myEntryOperators队列中加入一个新的Operator--OpStartErrorTone,
  //从而被processUaTimerEvent过程扑捉到,最后通过SendEvent发送到执行队列里去。
  if( processUaTimerEvent( uaTimerEvent ) )
  {
  return;
  }
  sendEvent( nextEvent );
  return;
  }
  assert( 0 );
  
  }
  
  2.5.1.1 processSipEvent
    顾名思义,processSipEvent方法是对队列中的SIP消息进行处理,我们来看下面的程序:
  bool UaBuilder::processSipEvent( const Sptr < SipEvent > sipEvent )
  {
  Sptr < StatusMsg > statusMsg;
  statusMsg.dynamicCast( sipEvent->getSipMsg() );
  // 检验是否为返回的状态码(主要是对Notify,Subscribe,Register三种状态进行单独处理)
  //下面做具体介绍
  if ( statusMsg != 0 )
  {
  if( handleStatusMsg( sipEvent ) )
  {
  return true;
  }
  }
  //在这里表示接收到一个SIP的消息,
  //检验是否为一个SIP的消息而不是一个状态(例如是否为Invite命令)
  /// Let's check the call info, now
  callId = sipEvent->getSipCallLeg()->getCallId();
  callInfo = calls->findCall( callId );
  if ( callInfo == 0 )
  {
  //下面分成两种状况进行讨论,一种是接受到Invite的消息,一种是接收到一个普通的
  //命令,例如
  Sptr < InviteMsg > inviteMsg;
  inviteMsg.dynamicCast( sipEvent->getSipMsg() );
  if ( inviteMsg == 0 )
  {
  //假如大家在这里有什么希奇的话没有必要,为什么除了inviteMsg以外的所有的消
  //息都不处理呢?其实这些消息都在SipThread这个程序中处理了,在Ua这个大状态
  //机中所有的状态都是以Invite这个消息作为启动的。每一个INVITE启动一个系列的//消息和状态。
  return true;
  }
  else
  {
  //收到一个Invite消息,这个时候我们就要进入相应的处理机制中了;
  callInfo = calls->newCall
  ( sipEvent->getSipCallLeg()->getCallId() );
  assert( callInfo != 0 );
  callInfo->setFeature( stateMachine );
  //假如进入的状态是自动呼叫(Auto Call)或者是自动应答(Auto Answer)状态(这
  //两种状态的确定要在CFG文件中体现)
  if ( UaConfiguration::instance()->getLoadGenOn() )
  {
  /// Assume this is a new call...
  /// Also assume that we are not in use.
  callInfo->setState( stateMachine->findState( "StateAutoIdle" ) );
  
  //StateAutoIdle这个状态是一个自动应答和自动呼叫(按照呼叫列表)时候的状态,这里我
  //们不做介绍,它本身和手动呼叫是非常相似的。
  }
  else // LoadGen is off
  {
  //下面这个程序会进入等待远端SIP事件和本地呼叫事件的状态StateIdle
  if( handleCallWaiting( callInfo ) )
  {
  cpLog( LOG_ERR, "Returned from handleCallWaitingn" );
  return true;
  }
  }
  } // lots of brackets!
  }
  return false;
  } /// UaBuilder::processSipEvent
  
  handleStatusMsg在做什么?
  
    前面我们已经作了简单的介绍,这个函数的主要目的是在处理Rgister,Notify,和Subscribe等几个状态,并且分别调用他们的处理机;
    Rgister调用它的处理机:
    handleRegistrationResponse他的主要作用是处理返回的各种Rgister状态,例如200,4XX或者是100等状态,另外它还负责在作为Mashal Server的时候转发各种状态时候,重新设定EXPire的值;另外要注重的是在Register中增加了一个新的返回--Trying这个是非常合理的,非凡是大型网络中,对服务器端的性能判定很有效,所以使用协议栈的同志能好好利用这个机制;另外假如发挥的值是401/407状态(未授权),还需要调用authenticateMessage做相应的处理,以返回的(401/407)状态中所带的密钥加密新的Rgister消息,发送给Register服务器重新进行授权判定;有爱好的可以看看BaseAuthentication中的addAuthorization函数。在介绍UaMarshal和Redirect Server的时候会着重讨论这个问题。
  
    注明:Subscribe的处理机在Feature Server章节里面在再具体介绍)。
  
  2.5.1.2 processUaDeviceEvent
  
    前面说了,processUaDeviceEvent主要是用来处理本地的设备事件,最主要就是处理摘机信号,在这里程序的流程我就不具体的列出,不过我们从主要的程序主体部分可以看出:
  
    在uaDeviceEvent->type == DeviceEventHookUp也就是检测了摘机以后,程序会采取某些必要的方式取得CallID(主要是通过CFG文件),最后让程序进入状态机的StateIdle状态,这个状态是接收和发送消息的初始状态,我们可以在后面将会重点介绍这个状态;
  
  2.5.1.3 processUaDigitEvent
  
    也是主要通过判定CFG文件中的LoadGen_On的参数是On或者是Off来决定是否进入StateAutoIdle状态,或者是StateAutoRS状态(自动通过Marshal Server进行中转所有的SIP的消息和状态,在Marshal Server的时候会做具体的介绍)。
  
  2.5.1.4 processUaTimerEvent
  
    这个的流程也实在没有什么好说的,前面也有了一定的介绍,假如大家对这些还有不明白的话,可以看一下SIP协议中Trying过程的走势,主要是对超时处理部分的介绍,就会明白(按照前面所说的UaBuilder::Process中关于SIP命令消息超时的介绍部分)。
  
  2.5.2 SipThread的Run方法:
  
  Void SipThread::thread()
  {
  … …
  while ( true )
  {
  try
  {
  //接收所发送的消息,并且预备置入相关的队列中;
  Sptr < SipMsgQueue > sipRcv( mySipStack->receive(1000) );
  
  if ( sipRcv != 0 )
  {
  Sptr < SipMsg > sipMsg = sipRcv->back();
  
  if ( sipMsg != 0 )
  {
  //根据本地的地址来检查是否发生了路由环路
  if ( discardMessage(sipMsg) )
  {
  continue;
  }
  // 在这里的myOutputFifo就是 myCallProcessingQueue(异地输入消息的队
  //列),在Workthread构建的时候会把这个队列带入作为处理参量
  Sptr < SipEvent > nextEvent = new SipEvent(myOutputFifo);
  if ( nextEvent != 0 )
  {
  //以下就是把新收到的消息载入队列当中。
  nextEvent->setSipReceive(sipRcv);
  nextEvent->setSipStack(mySipStack);
  if(myCallLegHistory) nextEvent->setCallLeg();
  myOutputFifo->add(nextEvent);
  }
  }
  }
  else
  {
  … …
  }
  }
  
  catch ( VException& v)
  {
  … …
  }
  catch ( ... )
  {
  … …
  }
  
  if ( isShutdown() == true )
  {
  return;
  }
  }
  }
  
  2.5.2.1 SIP消息的接收/发送缓冲技术
  
  a. 负责接收的主要程序体:
  Sptr < SipMsgQueue > sipRcv( mySipStack->receive(1000) );这个方法就是利用SipTransceiver的receive方法接收SIP的消息;
  Sptr < SipMsgQueue > SipTransceiver::receive(int timeOut)
  {
  Sptr < SipMsgQueue > msgQPtr = 0;
  //以下是设立超时参数,假如发生超时,那么就让该命令无效;
  timeval start, now;
  
  if ( timeOut >= 0 )
  {
  gettimeofday(&start, 0);
  }
  
  while (msgQPtr == 0)
  {
  int timePassed = 0;
  if ( timeOut >= 0 )
  {
  gettimeofday(&now, 0);
  
  timePassed = ( now.tv_sec - start.tv_sec ) * 1000
  + ( now.tv_usec - start.tv_usec ) / 1000;
  
  if (timePassed >= timeOut)
  {
  return 0;
  }
  }
  recvdMsgsFifo.block(timeOut);
  
  if ( !recvdMsgsFifo.messageAvailable() )
  {
  continue;
  }
  
  SipMsgContainer *msgPtr = recvdMsgsFifo.getNext();
  if ( msgPtr == 0)
  {
  assert(0);
  cpLog(LOG_CRIT, "received NULL");
  continue;
  }
  #if 1
  if ( natOn == true)
  {
  //这里是一个非常有意思的地方,虽然再程序主体中将它设定为False,也就是我们就
  //不能采用NAT转换了,不过我还是想介绍一下,它主要是用在假如UA是一个标准
  //的网关,或者是路由器设备的情况之下,在这个时候,它主要做各个消息包的转
  //译工作,把路由(Via List)改成下一跳的IP地址和端口地址;
  SipVia natVia = msgPtr->msg.in->getVia(0);
  LocalScopeAllocator lo;
  string addr1 = natVia.getHost().getData(lo);
  string addr2 = msgPtr->msg.in->getReceivedIPName().getData(lo);
  NetworkAddress netaddr1(addr1);
  NetworkAddress netaddr2(addr2);
  if ( netaddr1.getHostName() != netaddr2.getHostName())
  {
  natVia.setReceivedhost(msgPtr->msg.in->getReceivedIPName());
  natVia.setReceivedport(msgPtr->msg.in->getReceivedIPPort());
  //remove the first item from the via list
  msgPtr->msg.in->removeVia(0);
  //insert natvia in the vector via list
  msgPtr->msg.in->setVia(natVia, 0);
  }
  }
  #endif
  //---NAT
  /* *********************************************************************/
  
  
  SipMsgQueue *msgQ = 0;
  Sptr sipPtr = msgPtr->msg.in;
  
  if(msgPtr->msg.in->getType() == SIP_STATUS)
  //这两个是处理返回消息队列的函数,下面将重点介绍
  msgQ = sentRequestDB.processRecv(msgPtr);
  else
  msgQ = sentResponseDB.processRecv(msgPtr);
  //更新SNMP命令队列,并向SNMP网管中心发送接收的消息队列;
  if(msgQ)
  {
  msgQPtr = msgQ;
  
  //need to have snmpDetails for this.
  if (sipAgent != 0)
  {
  updateSnmpData(sipPtr, INS);
  }
  }
  else if(msgPtr->msg.in != 0)
  {
  send(msgPtr);
  }
  else if(msgPtr->msg.out.length())
  {
  send(msgPtr);
  }
  else
  … …
  }
  }
  
  b.描述接收/发送SIP消息队列的主要类:
    SipSentRequestDB:: processRecv和SipSentRequestDB::processSend是一对相互的方法,
  另外还有SipSentResponseDB:: processRecv和SipSentResponseDB::processSend是用来记忆状态/消息的发送和接受的,在这里和Request的结构基本相同,就不做累述了;前者处理发送的SIP消息队列,后者处理接收的SIP消息队列,为了实现高效率的处理SIP的队列,在程序中大量采用了HASH表的方法,由于这个部分的程序非常的多,我不想一一把他们罗列出来,在这里就做一下简单的一个浏览:
  
    HASH队列的抽象:在这里有三个用于表示HASH表的类:
    SipTransLevel1Node,SipTransLevel2Node,SipTransLevel3Node;
  
    第一个是表的入口,它的组成由:目的地址 NameAddress源地址 From以及CallID三个部分叠加而成;
  
    第二个是表的索引,包括CSeq和Via 路由表
  
    第三个就是具体的消息对了,也就是一个呼叫命令组的列表;详见下图:
     
  
  我们下面一一个简单的例子来描述一下一个INVITE消息的处理过程:
     
  
  A. 接收到一个Invite Message/发送一个180状态的情况的情况:
    1. 在UDP通道收到一个INVITE消息
    2. 创建了一个InvMsg,同时发送到SipSentResponseDB中做备份,我们要检查在这里有没有重复的副本;
    3.假如没有重复,那么InvMsg就放入RecvFifo中,预备让应用层进行处理;
    4.应用层通过SipTransciever接收到了InvMsg并且做出相应的处理;
    5.应用层产生了180回应到SipSentResponseDB中备份,
    6.180在SndFifo中排队,并且调用SipTransceiver中的SendReply方法回送消息
  
  B.从对方接收到一个100(Trying)状态的作为向对方发送Invite消息回应的情况:
    1. 在UDP通道收到一个INVITE的状态;
    2. 创建了一个StatusMsg,同时发送到SipSentResquestDB中做备份,我们要检查在这里有没有重复的副本;
    3.假如没有重复,那么StatusMsg就放入RecvFifo中,预备让应用层进行处理;
    4.应用层通过SipTransciever接收到了StatusMsg并且做出相应的处理;
    5.应用层产生了ACK回应到SipSentResquestDB中备份,
    6.180在SndFifo中排队,并且调用SipTransceiver中的SendAsync方法回送ACK消息,
  
  c.在存在一个呼叫重新定向的情况:
    *我们下面来看一个更加复杂一点的情况:
     
  
  1>SipSendtRequestDB::processSend方法:
  
    我们可以做一个很简单的举例,大家就对这两个方法有比较深入的了解了,可以以上面的Diagram1来做一个很好的例子比如,Marshal Server开始发送一个Invite的消息,由SipSendtRequestDB::processSend来进行处理,同时并且把这个消息装入SipMsgContainer中,然后消息被插入到SipTransactionList队列中:
  
    topNode->findOrInsert(id)->val->findOrInsert(id)
    最后放在SipTransLevel1Node,SipTransLevel2Node,SipTransLevel3Node形成一个新的节点。
  
  2>SipSentRequestDB:: processRecv方法:
    例如我们接收了一个回应100 Trying这个回应的处理自然落在下面的这个部分:
  int statusCode = response->getStatusLine().getStatusCode();
  if((statusCode < 200)
  ((SipTransceiver::myAppContext == APP_CONTEXT_PROXY) &&
  (statusCode == 200) &&
  (response->getCSeq().getMethod() == INVITE_METHOD) ) )
  … …
  retVal = new SipMsgQueue;
  retVal->push_back(msgContainer->msg.in)
  
    单纯的把消息队列返回上面的应用层;
    后续的180(Ringing)也是如此直接返回应用层;
    但是到了接受到200(OK),那么处理的方式就大不一样了因为OK以后命令交互阶段已经告一段落,那么我们通过SipTransactionGC::instance()-> collect的后台方法处理(Thread线程),根据Delay的时间的变化:如invCleanupDelay等等,删除当前的一些队列中消息所占用的内存(垃圾处理),(具体处理机制可以参看SipTransactionGC::thread()这个后台处理掉一些孤独的消息,例如有Request没有Response的等等,并且根据各个消息所占用的Delay时间来释放他们);
  
    但是假如没有收到200呢?假设我们收到了302(呼叫转移)呢?(例如在上面Diagram 1中所表现的那样)
  
  答案在这里:
  else if(response->getStatusLine().getStatusCode() >= 200)
  {
  if(level3Node->val->msgs.response)//这里是检验在消息队列中是否有应答
  //产生,也就是Diagram 1中的Second Phase的情况,(第二个Invite消息)
  {
  SipTransactionList::SipTransListNode *
  curr = 0;
  if(level3Node->val->myKey == INVITE_METHOD)
  {
  curr = level2Node->val->level3.getLast();
  while(curr)
  {
  // look for the ACK message
  if(curr->val->myKey == ACK_METHOD &&
  curr->val->msgs.request)
  {
  cpLog(DEBUG_NEW_STACK,"duplicate message: %s",
  msgContainer->msg.out.logData());
  //通过第一个ACK来复制第二个ACK,使用上二者完全相同,
  msgContainer->msg.in = 0;
  msgContainer->msg.out =
  curr->val->msgs.request->msg.out;
  msgContainer->msg.type
  = curr->val->msgs.request->msg.type;
  msgContainer->msg.transport =
  curr->val->msgs.request->msg.transport;
  msgContainer->msg.netAddr =
  curr->val->msgs.request->msg.netAddr;
  
  msgContainer->retransCount = FILTER_RETRANS_COUNT;
  
  break;
  }
  curr = level2Node->val->level3.getPrev(curr);
  }
  
    很明显复制一个ACK消息预备进行下一个新的Invite的发送,当然这个是要在有ACK发送以后才可以进行,假如没有那么我们可以假定ACK正处在Processing状态;
  
  if(!curr)
  {
  msgContainer->msg.in = 0;
  msgContainer->msg.out = "";
  msgContainer->retransCount = 0;
  }
  }
  else
  … …
  
    在这个else下面所表示的处理机制是在第一个Message发送出去以后回应大于200的情况,也就是在Diagram 1中First Phase的情况,也就是发出第一个302的情况,在下面有一行语句:
  
    msgContainer->msg.out=msgContainer->msg.in->encode()
  
    它的主要目的是用于形成ACK应答,
  
    另外后面介绍Marshal Server的时候向异地发送Invite的时候返回4XX的回应,一般都是4XX等恶名招著的Response不会有其他的,本地一般采取的处理就是向应用层汇报,并且消除Hash队列里的所有驻留的消息。
  
    大家可以根据上面介绍的方法实验一下其他的情况,基本上都是合适的.
  目前来说这个处理机制并不使最优的,非凡是在服务器的状态,某些情况事实上并没有
  一个具体的处理方法:例如4XX的回应,可能会造成超时等待过长。
  
  2.6 在User Agent中的四个重要实例的Run方法:
    HeartLessProxy的两个Run方法都介绍完毕了,现在我们来看下面将要启动的四个Run过程:
  
  2.6.1 媒体设备启动
    DeviceThread->run(); //调用SoundcardDevice::hardwareMain(0)
  
    第一个是调用Sound Card的处理进程,它最主要的用处是返回各种按键的处理信息,他的具体的作用可以参看程序,和具体的操作手册,非常的简单易懂,不用具体介绍,不过要注重的一点是,在程序中,启动按键事件的检测是通过RTP/RTCP的事件触发的,(很明显,例如在通话的时候按下z表示挂机,必须是在有RTP/RTCP事件),说简单了,没有设备,键盘事件无法触发。
  
  2.6.2 启动RTP线程,用于对RTP/RTCP包的接收和发送治理;
    rtpThread->run //调用SoundCardDevice::processRTP()
  
    参看RtpThread实例化的过程可以看出,实际上就是调用SoundCardDevice的processRTP过程。
  
  SoundCardDevice::processRTP ()
  {
  … …
  if (audioStack == 0)
  {
  … …
  return;
  }
  
  bool bNothingDo = true;
  
  RtpSessionState sessionState = audioStack->getSessionState();
  
  if ( sessionState == rtp_session_undefined )
  {
  deviceMutex.unlock();
  … …
  return;
  }
  if( sessionState == rtp_session_recvonly
  sessionState == rtp_session_sendrecv )
  {
  // audioStack就是RtpSession,在这里它是在构建这个声音设备的时候,就创建它了。
  //这里表示从一个创建好的RTP会话中接收一帧数据,
  inRtpPkt = audioStack->receive();
  if( inRtpPkt )
  {
  //这里的声卡目前只能接受一种压缩方式PCM,所以只能解析这一种最常用的,
  if( inRtpPkt->getPayloadType() != rtpPayloadPCMU
  //RTP的采样频率是否为要求的频率,例如为20ms
  inRtpPkt->getPayloadUsage() != NETWORK_RTP_RATE )
  {
  cpLog(LOG_ERR,"Received from RTP stack incorrect payload type");
  }
  //将数据输出到声卡,
  writeToSoundCard( (unsigned char*) inRtpPkt->getPayloadLoc(),
  inRtpPkt->getPayloadUsage() );
  bNothingDo = false;
  
  … …
  }
  }
  
  // 这里是发送一帧数据;
  if( sessionState == rtp_session_sendonly
  sessionState == rtp_session_sendrecv )
  {
  int cc;
  if( audioStack->getRtcpTran() )
  { //假如有发送零声的情况,例如零声回送被叫端,这里在OpRing里通过//sendRemoteRingback过程来实现向远端回送零声(sendRingback=True)
  if( sendRingback )
  {
  cc = getRingbackTone( dataBuffer, RESID_RTP_RATE );
  #ifdef WIN32
  Sleep(15);
  #endif
  }
  else
  {//从声卡中读入一帧数据,按照cfg文件中规定的采样标准
  cc = readFromSoundCard( dataBuffer, RESID_RTP_RATE );
  }
  if ((cc > 0) && audioStack)
  {//将这帧数据(毛数据,未压缩的作成RTP包发送出去);
  audioStack->transmitRaw( (char*)dataBuffer, cc );
  bNothingDo = false;
  }
  … …
  }
  }
  … …
  deviceMutex.unlock();
  
  return;
  }
  
  2.6.3 合法用户列表的获取(Redirection Server专用)
  
    第三个过程是featureThread->run,,这个过程主要是用在向重定向服务器(Redirection Server)和Provisioning Server中的Feature线程,它实质上是调用 subscribe -Manager->subscribeMain,主体程序部分是向Provisioning Server发送Subscribe消息,在这个循环中会反复的发送SubScribe消息到Provision Server中去,稍后我们要介绍的UaBuilder::handleStatusMsg(UaBuilder::processSipEvent中)过程会将会处理从Provision Server 返回的Notify消息,关于Subscribe/Notify消息对的介绍我们可以参看在Vocal中的相关介绍,它的作用范围是在一个普通的UA向Marshal Server进行注册或者是证实的时候,Marshal Server同时向Redirection Server发出Register消息,并且由Redirection Server向Provisioning Server发送Subscribe消息,对用户列表进行检测;我们可以举一个例子来说明这个过程:
     
  
  我们来看Diagram.7
  
  1> 在A阶段当启动Redirection Server(RS)的时候,RS向Provisioning Server(PS)发送SubScribe消息,取得合法的用户列表;
  2> 在B阶段,UA端向Marshal Server发送Register消息,以确认自己是否在合法用户列表内;
  3> 在C阶段,RS将通过Subscribe/Notify命令对把该用户的呼叫特性列表(呼叫等待,呼叫转接,语音邮件,呼叫前转,禁止呼叫等信息)得到该用户的呼叫特性;
  我们在Redirection Server这一章内将具体介绍Subscribe/Notify命令对。
  
  2.6.4 监测线程:
    一个调用的RUN方法loadGenThread->run是一个监测线程,检查各种回应和请求消息,并记录在LOG文件中。
  
  2.6.5 自动呼叫
    在loadGenThread->run后面的程序实现了一个自动在预定时间内发送INVITE消息的过程,大家有爱好可以参看OpAutoCall类,当在UserAgent::Run()中通过检测Cfg文件,通过setLoadGenSignalType(LoadGenStartCall)设定了一个公共变量以后,我们可以发现系统将自动进入OpAutoCall操作,并且启动INVITE开始呼叫。
    
  
    好了,通过上面的介绍后我们需要知道如何让系统进入Idle状态,在这个状态中系统处于一种"等待"的状态,接收本地的命令输入,和远端的消息;这个状态是所有后续状态的一个初始阶段,在上述程序中我们可以在processSipEvent过程中找到handleCallWaiting子程序,就在该过程中让系统进入Idle状态;见下面的程序:
  … …
  if ( UaConfiguration::instance()->getLoadGenOn() )
  {
  callInfo->setState
  ( stateMachine->findState( "StateAutoIdle" ) );
  }
  else // LoadGen is off
  {
  if( handleCallWaiting( callInfo ) )
  {
  return true;
  }
  … …
  
  (未完待续)