Как сохранить VOIP-сокет в фоновом режиме? - PullRequest
20 голосов
/ 13 мая 2011

Требование к моему приложению : по некоторым причинам я должен поддерживать подключение через сокет, чтобы запускать локальные уведомления при отправке на сервер без использования push-уведомлений (APN). Поэтому я использую фоновую функцию VOIP iPhone для поддержания сокетного соединения.

1. Я настроил поток для VOIP, чтобы сохранить соединение сокетов для работы в фоновом режиме, так какое значение тайм-аута мне следует установить? Разъединится ли сокет-соединение после истечения времени ожидания? Как мне сделать так, чтобы мое приложение все время слушало сокет .?

Конфигурация потока клиента выглядит следующим образом,

NSString *urlStr = @"http://192.168.0.108";
NSURL *website = [NSURL URLWithString:urlStr];
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)[website host], 1234, &readStream, &writeStream);

CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);    

NSInputStream *inputStream = (NSInputStream *)readStream;
NSOutputStream *outputStream = (NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[inputStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType] ;
[outputStream setDelegate:self];
[outputStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType] ;
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];

2. Должен ли я повторно подключить поток в обработчике приложения DidEnterBackground:

   [[UIApplication sharedApplication] setKeepAliveTimeout:86400 handler:^(void) 
{

    if (inputStream)
        [inputStream close];
    if (outputStream)
        [outputStream close];


    urlStr = @"http://192.168.0.108";
    website = [NSURL URLWithString:urlStr];
    CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)[website host], 1234, &readStream, &writeStream);
    CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
    CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);    
    inputStream = (NSInputStream *)readStream;
    outputStream = (NSOutputStream *)writeStream;
    [inputStream setDelegate:self];
    [inputStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType] ;
    [outputStream setDelegate:self];
    [outputStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType] ;
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [inputStream open];
    [outputStream open];

    }];

3. Скажите, что мой сервер перезагружается, а приложение работает в фоновом режиме, как мне обеспечить соединение? Если соединение Wi-Fi в моем iPhone или если я отключаю серверное приложение, соединение будет закрыто, так что я должен предпринять, чтобы заставить мое приложение работать согласно ожиданиям?

Ответы [ 2 ]

19 голосов
/ 14 мая 2011

Вы также должны убедиться, что вы установили в своем файле pList

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

Сокетом будет управлять iOS, пока ваше приложение находится в фоновом режиме.Ваше приложение получит процессорное время, как только в сокете появятся данные.Итак, в runLoop я проверяю ht

. В моем случае протокол сигнализации работает в отдельном потоке, поэтому я запускаю runLoop самостоятельно

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

и останавливаю его при необходимости:

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

Для сокетов в runLoop я настроил функции обработчика перед планированием их в 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;
  }

Это функции, которые будут вызываться, когда ваш сокет будет иметь некоторыеинформация для вас и даже если ваше приложение в фоновом режиме:

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
  }
}

Чтобы сервер знал, что клиент еще жив, мы отправляем команду ping на сервер каждые 10 минут, чтобы обработчик KeepAlive был установлен на 600. ВыМожно использовать другие значения для экономии заряда батареи, но это ухудшит обнаружение разъединений на стороне клиента и сервера.И увеличит время между отключением и повторным подключением.

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

  SchedulePing(0);
}

Где SchedulePing (0) будет выполняться следующим образом:

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

И StartLongBGTask - это

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

Это необходимо, чтобы убедиться, что приложение не будет приостановлено до отправки пинга и ожидания ответа на пинг с сервера.Также, если сокет уже отключен, может случиться так, что потребуется повторное подключение, которое займет некоторое время и требует выполнения процесса в фоновом режиме.

Но убедитесь, что фоновые задачи должным образом освобождены, когда вам больше не нужноих.Другое мудрое приложение будет уничтожено системой при превышении времени ожидания bg.

2 голосов
/ 06 августа 2014

Apple предоставила подробную информацию об этом в официальной документации. Вы можете найти ее здесь https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/AdvancedAppTricks/AdvancedAppTricks.html

Согласно документации

Существует несколько требований для реализации приложения VoIP:

1.Добавьте ключ UIBackgroundModes в файл Info.plist вашего приложения. Задайте значение этого ключа для массива, который включает строку voip.

2.Настройте один из сокетов приложения для использования VoIP.

3. Перед тем как перейти к фону, вызовите setKeepAliveTimeout: handler: метод для установки обработчика, который будет выполняться периодически. Ваше приложение может использовать этот обработчик для поддержания своего сервисного соединения.

4.Настройте аудио сеанс для обработки переходов в активное использование и из него.

5.Чтобы обеспечить лучшее взаимодействие с пользователем на iPhone, используйте инфраструктуру базовой телефонии, чтобы настроить свое поведение в отношении телефонных звонков на сотовой основе; см. Базовую справочную систему телефонии.

6.Чтобы обеспечить хорошую производительность для вашего приложения VoIP, используйте инфраструктуру конфигурации системы, чтобы обнаружить изменения в сети и дать вашему приложению как можно больше сна.

Включение значения voip в ключ UIBackgroundModes позволяет системе узнать, что оно должно позволять приложению работать в фоновом режиме по мере необходимости для управления сетевыми сокетами. Этот ключ также позволяет вашему приложению воспроизводить фоновый звук (хотя включение значения звука для ключа UIBackgroundModes все еще рекомендуется). Приложение с этим ключом также запускается в фоновом режиме сразу после загрузки системы, чтобы обеспечить постоянную доступность служб VoIP. Для получения дополнительной информации о ключе UIBackgroundModes см. Ссылку ключа списка свойств информации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...