首页 文章

如何在后台维护VOIP套接字连接?

提问于
浏览
20

My App Requirement :由于某些原因,我应该维护套接字连接以在服务器推送时触发本地通知,而不使用推送通知(APN) . 所以我使用iPhone的VOIP后台功能来维护套接字连接 .

1. I have configured a stream for VOIP in order to persist socket connection to run in background, so what Timeout value should I set? Will the socket connection terminates once the timeout expires? How do I make my application to listen to the socket all the time.?

客户端流配置如下,

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. Should I reconnect the stream in the handler 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. Say my server restarts and the app is in background, how do I ensure the connection? 如果iPhone中的Wi-Fi连接或我终止服务器应用程序的连接将被关闭,那么我应该采取什么措施让我的应用按预期工作?

2 回答

  • 2

    您还需要确保已在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回复之前不会挂起应用程序 . 此外,如果套接字已经断开连接,则可能需要重新连接,这将花费一些时间并且需要proccess在后台运行 .

    但是,当您不再需要它们时,请务必正确释放后台任务 . 当超过bg超时时,系统将杀死其他明智的应用程序 .

  • 19

    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应用程序的良好性能,请使用系统配置框架检测网络更改并允许应用程序尽可能地休眠 .

    在UIBackgroundModes键中包含voip值可让系统知道它应该允许应用程序在后台运行以管理其网络套接字 . 此键还允许您的应用播放背景音频(尽管仍然鼓励包含UIBackgroundModes键的音频值) . 具有此密钥的应用程序也会在系统启动后立即在后台重新启动,以确保VoIP服务始终可用 . 有关UIBackgroundModes键的详细信息,请参阅信息属性列表键参考 .

相关问题