web-dev-qa-db-ja.com

バックグラウンドでVOIPソケット接続を維持する方法は?

私のアプリの要件:何らかの理由でプッシュ通知(APN)を使用せずに、サーバープッシュでローカル通知をトリガーするためにソケット接続を維持する必要があります。そのため、iPhoneのVOIPバックグラウンド機能を使用してソケット接続を維持しています。

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。ハンドラー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];

    }];

。サーバーが再起動し、アプリがバックグラウンドにある場合、接続を確認するにはどうすればよいですか? iPhoneのWi-Fi接続またはサーバーアプリを終了すると、接続が閉じられます。アプリを期待どおりに機能させるには、どのような対策を講じる必要がありますか?

20
pradeepa

また、pListファイルにを設定していることを確認する必要があります

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

アプリケーションがバックグラウンドにある間、ソケットはiOSによって管理されます。ソケットで利用可能なデータがあるとすぐに、アプリケーションはCPU時間を受け取ります。したがって、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
  }
}

クライアントがまだ生きていることをサーバーに知らせるために、10分ごとにサーバーにpingコマンドを送信して、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;
}];

これは、pingを送信し、サーバーからのpingで応答を待つ前に、アプリケーションが一時停止されないようにするために必要です。また、ソケットがすでに切断されている場合は、再接続が必要になる可能性があります。これには時間がかかり、バックグラウンドでプロセスを実行する必要があります。

ただし、バックグラウンドタスクが不要になった場合は、適切に解放してください。それ以外の場合、bgタイムアウトを超えると、システムによってアプリケーションが強制終了されます。

19
Alexey Kozhanov

Appleは公式ドキュメントでこれに関する詳細を提供しています。ここで見つけることができます https://developer.Apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/AdvancedAppTricks/AdvancedAppTricks.html

ドキュメントによると

VoIPアプリを実装するには、いくつかの要件があります。

1.UIBackgroundModesキーをアプリのInfo.plistファイルに追加します。このキーの値を、VoIP文字列を含む配列に設定します。

2.VoIPで使用するためにアプリのソケットの1つを構成します。

3.バックグラウンドに移動する前に、setKeepAliveTimeout:handler:メソッドを呼び出して、定期的に実行されるハンドラーをインストールします。アプリはこのハンドラーを使用して、サービス接続を維持できます。

4.アクティブな使用への移行とアクティブな使用からの移行を処理するようにオーディオセッションを構成します。

5. iPhoneでのユーザーエクスペリエンスを向上させるには、コアテレフォニーフレームワークを使用して、携帯電話ベースの通話に関連する動作を調整します。コアテレフォニーフレームワークリファレンスを参照してください。

6. VoIPアプリのパフォーマンスを向上させるには、システム構成フレームワークを使用してネットワークの変更を検出し、アプリを可能な限りスリープ状態にします。

UIBackgroundModesキーにvoip値を含めると、ネットワークソケットを管理するために、必要に応じてアプリをバックグラウンドで実行できるようにする必要があることがシステムに通知されます。このキーを使用すると、アプリでバックグラウンドオーディオを再生することもできます(ただし、UIBackgroundModesキーのオーディオ値を含めることをお勧めします)。このキーを持つアプリは、VoIPサービスが常に利用可能であることを保証するために、システムの起動直後にバックグラウンドで再起動されます。 UIBackgroundModesキーの詳細については、「情報プロパティリストキーリファレンス」を参照してください。

2
Suraj K Thomas