9
votes

I have a VoIP app that uses a TCP service to wake it up on incoming calls. The TCP socket is created with this code fragment:

CFReadStreamRef read = NULL;
CFWriteStreamRef write = NULL;
...
CFStreamCreatePairWithSocketToHost(NULL,(__bridge CFStringRef)shost, port, &read, &write);
self.read = (__bridge NSInputStream*)read;
self.write = (__bridge NSOutputStream*)write;
if (![self.read setProperty:NSStreamNetworkServiceTypeVoIP
                     forKey:NSStreamNetworkServiceType]){
    [Log log:@"Could not set VoIP mode to read stream"];
}
if (![self.write setProperty:NSStreamNetworkServiceTypeVoIP
                      forKey:NSStreamNetworkServiceType]){
    [Log log:@"Could not set VoIP mode to write stream"];
}
self.read.delegate = self;
self.write.delegate = self;
CFRelease(read);
CFRelease(write);
[self.read scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.write scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.read open];
[self.write open];

I've also set the following:

  1. VoIP & Audio in the info plist
  2. Keep alive timer using [UIApplication sharedApplication] setKeepAliveTimeout
  3. UIRequiresPersistentWiFi = YES in the info plist (quite sure it's not required, but...)

This works well while the app is in the foreground, and even works well in the background for a few minutes, but after a few minutes - the app does not receive any new TCP messages. It doesn't work on wifi or 3G, same result for both.

I also tried setting the property just to the read stream (though the read and write point to the same socket). Whenever I receive data on the TCP or send data I also start a short background task. BTW - everything takes place on the main thread.
I've checked if the app crashes - it doesn't.
The same behavior can be observed while debugging on the device - after a while - nothing is received (no crashes, warnings, anything).

What am I doing wrong?

3
may worth double-check the correct VOIP flag/tag at info plist...dklt
Double and triple checked... Everything works for a while and then stops.Moshe Gottlieb
any chance somehow server closes the socket after timeout? ie: server fault instead of app?dklt
No.. wiresharked the server, messages are coming out and the tcp connection is not dropped.Moshe Gottlieb
I am sure you have checked steps 1-6 for VOIP app. REF:: developer.apple.com/library/ios/#documentation/iphone/…dklt

3 Answers

3
votes

Try the following code.Make sure you have only one voip socket in your app.

CFReadStreamRef readStream;
CFWriteStreamRef writeStream;

CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)@"1.2.3.4",9999, &readStream, &writeStream);

CFReadStreamSetProperty(readStream,kCFStreamNetworkServiceType,kCFStreamNetworkServiceTypeVoIP);
CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);

inputStream = (NSInputStream *)readStream;
[inputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];

outputStream = (NSOutputStream *)writeStream;
[outputStream setDelegate:self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
3
votes

Looks like your code should work. But there may be two technical problems I can think of:

  1. If you try this from LAN connection, while app in background the LAN router can close passive TCP connection because, in this case, SIP stack(guess you use SIP protocol) can't send data keep alive every 15 to 30 secs like it would in foreground.

  2. Less likely, suppose you know what you doing, but since registration keep alive can be triggered only once in 10 minutes while in background, make sure that SIP server allows such a long expire period and you define it right in registration message.

0
votes

In ViewController.h file add

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

In ViewController.m file add after @implementation ViewController

const uint8_t pingString[] = "ping\n";
const uint8_t pongString[] = "pong\n";

Add following code in viewDidLoad

CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(@"192.168.0.104"), 10000, &readStream, &writeStream);

//in above line user your MAC IP instead of 192.168.0.104

self.sentPing = NO;
//self.communicationLog = [[NSMutableString alloc] init];
self.inputStream = (__bridge_transfer NSInputStream *)readStream;
self.outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[self.inputStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType];
[self.inputStream setDelegate:self];
[self.outputStream setDelegate:self];
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
[self.outputStream open];

//After every 10 mins this block will be execute to ping server, so connection will be live for more 10 mins
[[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{
    if (self.outputStream)
    {
        [self.outputStream write:pingString maxLength:strlen((char*)pingString)];
        //[self addEvent:@"Ping sent"];
    }
}];


- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
    case NSStreamEventNone:
        // do nothing.
        break;

    case NSStreamEventEndEncountered:
        //[self addEvent:@"Connection Closed"];
        break;

    case NSStreamEventErrorOccurred:
        //[self addEvent:[NSString stringWithFormat:@"Had error: %@", aStream.streamError]];
        break;

    case NSStreamEventHasBytesAvailable:
        if (aStream == self.inputStream)
        {
            uint8_t buffer[1024];
            NSInteger bytesRead = [self.inputStream read:buffer maxLength:1024];
            NSString *stringRead = [[NSString alloc] initWithBytes:buffer length:bytesRead encoding:NSUTF8StringEncoding];
            stringRead = [stringRead stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];

            //[self addEvent:[NSString stringWithFormat:@"Received: %@", stringRead]];


            //if server response is 'call' then a notification will go to notification center and it will be fired
            //immediately and it will popup if app is in background.
            if ([stringRead isEqualToString:@"call"])
            {
                UILocalNotification *notification = [[UILocalNotification alloc] init];
                notification.alertBody = @"New VOIP call";
                notification.alertAction = @"Answer";
                //[self addEvent:@"Notification sent"];
                [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
            }
            //else if ([stringRead isEqualToString:@"ping"])
            //{
            //if server response is 'ping' then a sting 'pong' will go to server immediately
            //[self.outputStream write:pongString maxLength:strlen((char*)pongString)];
            //}
        }
        break;

    case NSStreamEventHasSpaceAvailable:
        if (aStream == self.outputStream && !self.sentPing)
        {
            self.sentPing = YES;
            if (aStream == self.outputStream)
            {
                [self.outputStream write:pingString maxLength:strlen((char*)pingString)];
                //[self addEvent:@"Ping sent"];
            }
        }
        break;

    case NSStreamEventOpenCompleted:
        if (aStream == self.inputStream)
        {
            //[self addEvent:@"Connection Opened"];
        }
        break;

    default:
        break;
}
}

Build your app and run

Open terminal in your MAC PC and write nc -l 10000 and press enter

$ nc -l 10000

Then write call and press enter