How to Maintain VOIP socket connection in background?

You also need to ensure that you have set the in your pList file

<key>UIBackgroundModes</key>
<array>
    <string>voip</string>
</array>

The socket will be managed by the iOS while your application is in the background. Your application will receive the CPU time as soon there is a data available in the socket. So in the runLoop I am checking ht

In my case the signaling protocol is working in a separate thread, so I am spinning the runLoop my self

  // Start runloop
  while (!m_needStop) 
  {
    CFRunLoopRun();
  }

And stopping it when needed:

  m_needStop = true;
  {
    QAutoLock l(m_runLoopGuard);
    if ( m_runLoop != NULL )
      CFRunLoopStop(m_runLoop);
  }

For the sockets in runLoop I have setup the handler functions before scheduling them into the runLoop:

  int nFlags = kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable | kCFStreamEventCanAcceptBytes | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
  CFStreamClientContext context;
  context.info = this;
  context.version = 0;
  context.release = NULL;
  context.retain = NULL;
  context.copyDescription = NULL;

  if ( !CFReadStreamSetClient(m_readStream, nFlags, NotificationProtocolHandler::ReadStreamCallback, &context) )
  {
    ReleaseStreams();
    return false;
  }

  if ( !CFWriteStreamSetClient(m_writeStream, nFlags, NotificationProtocolHandler::WriteStreamCallback, &context) )
  {
    ReleaseStreams();
    return false;
  }

These are the functions which will be called when your socket will has some info for you and even if your application in the background:

void NotificationProtocolHandler::ReadStreamCallback(CFReadStreamRef stream,
                                                     CFStreamEventType eventType,
                                                     void *clientCallBackInfo)
{      
  NotificationProtocolHandler* handler = (NotificationProtocolHandler*)clientCallBackInfo;
  switch (eventType)
  {
    case kCFStreamEventOpenCompleted:
      break;

    case kCFStreamEventHasBytesAvailable:
      handler->ProcessInput();
      break;

    case kCFStreamEventErrorOccurred:
      handler->ProcessConnectionError();
      break;

    case kCFStreamEventEndEncountered:
      handler->ProcessConnectionError();
      break;

    default:
      break; // do nothing
  }
}

void NotificationProtocolHandler::WriteStreamCallback(CFWriteStreamRef stream,
                                                      CFStreamEventType eventType,
                                                      void *clientCallBackInfo)
{
  NotificationProtocolHandler* handler = (NotificationProtocolHandler*)clientCallBackInfo;

  switch (eventType)
  {
    case kCFStreamEventOpenCompleted:
      handler->ProcessOutputConnect();
      break;

    case kCFStreamEventCanAcceptBytes:
      handler->ProcessReadyToWrite();
      break;

    case kCFStreamEventErrorOccurred:
      handler->ProcessConnectionError();
      break;

    case kCFStreamEventEndEncountered:
      handler->ProcessConnectionError();
      break;     

    default:
      break; // do nothing
  }
}

To make server aware that client is still alive we send the ping command to server every 10 minutes so the KeepAlive handler is set to 600. You can use other values to save the battery but it will make worse the detection of the disconnects on the client and server side. And will increase the time between the disconnect and reconnect.

BOOL scheduled = [app setKeepAliveTimeout:pingTimeout handler:^{ // Schedule processing after some time interval      

  SchedulePing(0);
}

Where SchedulePing(0) will be executed as following:

StartLongBGTask();
if ( avoidFinishBgTask != NULL )
  *avoidFinishBgTask = true;
m_pingTimer = CreateTimer(pingTimeout, PingTimerCallback); // result is ignored

And StartLongBGTask is a

m_bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{
  [[UIApplication sharedApplication] endBackgroundTask:m_bgTask];
  m_bgTask = UIBackgroundTaskInvalid;
}];

This is needed to make sure that application will not be suspended before sending the ping and waiting the reply on ping from the server. Also if socket is already disconnected it could happen that reconnect is needed which will take some time and needs proccess to be running in the background.

But make sure to free the background tasks properly when you don’t longer need them. Other wise application will be killed by the system when bg timeout will be exceeded.

Leave a Comment