【问题标题】:How to Maintain VOIP socket connection in background?如何在后台维护 VOIP 套接字连接?
【发布时间】:2011-05-13 05:13:20
【问题描述】:

我的应用要求:出于某些原因,我应该维护一个套接字连接以触发服务器推送的本地通知,而不使用推送通知(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];

    }];

3.假设我的服务器重新启动并且应用处于后台,我如何确保连接? 如果我的 iPhone 中的 Wi-Fi 连接或我终止服务器应用程序连接将被关闭,那么我应该采取什么措施来使我的应用程序按预期工作?

【问题讨论】:

  • 你在这里包含的代码,它是工作代码吗?
  • 仅供参考:iOS VOIP 背景模式现已在 iOS 10 及更高版本上弃用。
  • 嗨。我想写一个 crm web 应用程序。我想集成我的 crm 应用程序和客户 voip 实时通信。我该怎么做?我需要socket编程吗?

标签: iphone sockets background voip


【解决方案1】:

你还需要确保你已经在你的 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 超时时被系统杀死。

【讨论】:

  • 刚刚偶然发现一个帖子说一个应用程序被拒绝,因为它没有提供任何 IP 语音服务,而是使用了 VoIP 后台模式。对此有任何想法吗?因此,如果我正在开发一个 IM 应用程序并且我希望套接字即使在后台也处于活动状态,我是否仍然可以使用上述方法,即使我不支持任何语音功能?
  • 您的应用获得批准了吗? - 我也有类似的情况,但幸运的是我无论如何都可以使用企业分发
  • 对此也很好奇。如果您有一些故事,请加入!
【解决方案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:handler:方法来安装一个handler来周期性的执行。您的应用可以使用此处理程序来维护其服务连接。

4.配置您的音频会话以处理与活动使用之间的转换。

5.为确保在 iPhone 上获得更好的用户体验,请使用 Core Telephony 框架来调整您与手机通话相关的行为;请参阅核心电话框架参考。

6.为确保您的 VoIP 应用具有良好的性能,请使用系统配置框架来检测网络变化并让您的应用尽可能地休眠。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-06
    • 2017-01-09
    • 1970-01-01
    相关资源
    最近更新 更多