Configuring iOS VoIP application to run in sleep/background mode

Vinay Podili picture Vinay Podili · Dec 24, 2014 · Viewed 11.8k times · Source

I am developing a VoIP based iOS (7.1) application. It's underlying socket programming is written in C++ rather than objective C. The app runs fine in foreground but while putting in sleep/background mode it can't receive any communication from server. According to apple document we have to configure one of the app socket for VoIP usage. I am unable to figure out how to configure C++ sockets (Because there are many sockets; i.e. SSL, SIP, RESTful).

The intention is to run the app in sleep mode until it's killed. Tried few links and even couple of them from SO, but since I am novice I would want to have a step by step procedure for this configuration. [Note: In somewhere I found CoreFoudation framework, do I need to use that?]

Answer

KudoCC picture KudoCC · Dec 25, 2014

Edit: begin from iOS8, apple introduces PushKit framework to release the work we need to do to config VoIP application and it also reduces energy usage. You really need to migrate to it

There are tips for developing a VoIP App, reference from Apple

A Voice over Internet Protocol (VoIP) app allows the user to make phone calls using an Internet connection instead of the device’s cellular service. Such an app needs to maintain a persistent network connection to its associated service so that it can receive incoming calls and other relevant data. Rather than keep VoIP apps awake all the time, the system allows them to be suspended and provides facilities for monitoring their sockets for them. When incoming traffic is detected, the system wakes up the VoIP app and returns control of its sockets to it.

There are several requirements for implementing a VoIP app:

  1. Enable the Voice over IP background mode for your app. (Because VoIP apps involve audio content, it is recommended that you also enable the Audio and AirPlay background mode.) You enable background modes in the Capabilities tab of your Xcode project.
  2. Configure one of the app’s sockets for VoIP usage.
  3. Before moving to the background, call the setKeepAliveTimeout:handler: method to install a handler to be executed periodically. Your app can use this handler to maintain its service connection.
  4. Configure your audio session to handle transitions to and from active use.
  5. To ensure a better user experience on iPhone, use the Core Telephony framework to adjust your behavior in relation to cell-based phone calls; see Core Telephony Framework Reference.
  6. To ensure good performance for your VoIP app, use the System Configuration framework to detect network changes and allow your app to sleep as much as possible.

Enabling the VoIP background mode lets the system know that it should allow the app to run in the background as needed to manage its network sockets. This key also permits your app to play background audio (although enabling the Audio and AirPlay mode is still encouraged). An app that supports this mode is also relaunched in the background immediately after system boot to ensure that the VoIP services are always available.


The code below shows that how to configure app's socket for VoIP usage.

Step 1: Connect to server

uint16_t port ;
NSString *strIp ;
char ip[20] = {0} ;
memset(ip, 0, sizeof(ip)) ;
memcpy(ip, [strIp UTF8String], [strIp length]) ;

clientSocket = socket(AF_INET, SOCK_STREAM, 0) ;
struct sockaddr_in server_addr ;
bzero(&server_addr, sizeof(server_addr)) ;
server_addr.sin_port = htons(port) ;
server_addr.sin_addr.s_addr = inet_addr(ip) ;
server_addr.sin_family = AF_INET ;

int i = connect(clientSocket, (const struct sockaddr *)&server_addr, sizeof(server_addr)) ;
if (i >= 0) {
}

The server side code may be in C++ environment, but you can pass the clientSocket to the Objective-C instance, it is a int value.

Step 2: Create and config read and write stream

After connected to the server, you need to create a read & write stream based on clientSocket using CFStreamCreatePairWithSocket() and set the property of streams with NSStreamNetworkServiceTypeVoIP.

Define read and write streams and keep the strong reference them. Close and release them when the connection is lost.

@property (nonatomic, strong) NSInputStream *inputStream ;
@property (nonatomic, strong) NSOutputStream *outputStream ;

Then config streams:

CFReadStreamRef readStreamRef = nil ;
CFWriteStreamRef writeStreamRef = nil ;
CFStreamCreatePairWithSocket(NULL, clientSocket, &readStreamRef, &writeStreamRef) ; // the socket must have already been connected.
_inputStream = (__bridge_transfer NSInputStream *)readStreamRef ;
_outputStream = (__bridge_transfer NSOutputStream *)writeStreamRef ;
[_inputStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType] ;
[_outputStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType] ;
[_inputStream open] ;
[_outputStream open] ;

Make sure the socket is already connected before connecting with read & write stream.

Step 3:keep the connection

[[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{
    // the code to check if the socket is connected to server
    // if not, reconnect to server 
    // and re-set the read stream and write stream in step2
}] ;

When your app enters background, the socket is managed by system and when the server sends packet to your app, system wakes it up, passes the packet to it. You only have a few seconds to handle the data, so don't do much work here. As it's a VoIP app, the socket should be used to notify user that a call is incoming and you can push a local notification to make user be aware of that.

Because VoIP apps need to stay running in order to receive incoming calls, the system automatically relaunches the app if it exits with a nonzero exit code. (This type of exit could happen when there is memory pressure and your app is terminated as a result.) However, terminating the app also releases all of its sockets, including the one used to maintain the VoIP service connection. Therefore, when the app is launched, it always needs to create its sockets from scratch.

I have created an example project here, the relevant server side code here.