Подтвердить что ты не робот

Как поддерживать соединение сокетов VOIP в фоновом режиме?

Мое требование к приложению. Я должен поддерживать соединение сокета, чтобы инициировать локальное уведомление при нажатии на сервер без использования Push Notification (APN) по некоторым причинам. Поэтому я использую возможности фона VOIP для iPhone для поддержки подключения сокетов.

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

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

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. Должен ли я повторно подключить поток в обработчике applicationDidEnterBackground:

   [[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 или если я завершаю серверное приложение, соединение будет закрыто, поэтому какие меры следует принять, чтобы приложение работало в соответствии с ожиданиями?

4b9b3361

Ответ 1

Вам также необходимо убедиться, что вы установили в свой файл 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

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

В соответствии с документацией

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

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

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

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

4.Устройте свой аудио-сеанс для обработки переходов в активном режиме и из него.

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

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

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