11

I have the following code written in Objective-C that writes data to a socket. The server is running node.js on top of Ubuntu:

NSString *url = @"anIPAddress";        
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;            
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)url, 9000, &readStream, &writeStream);
self.inputStream = (NSInputStream *)readStream;            
self.outputStream = (NSOutputStream *)writeStream;
[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];

I am able to connect to the server and send information. However I noticed the connection times out after a few minutes (I think 5 minutes or so?). How can I keep this connection alive? I know there is a disconnect because the same thing happens if I connect to the server under Terminal, I have to keep that connection alive. I imagine the same thing is happening in my code. Thanks for your help!

Hahnemann
  • 4,378
  • 6
  • 40
  • 64
  • For Objective-C code that works, please refer to the [following answer](http://stackoverflow.com/a/25725181)... – Despotovic Feb 03 '16 at 23:08

4 Answers4

6

The simplest answer is to try to use the SO_KEEPALIVE socket option.

Broken connections can be hard to detect without data flowing between the end-points which is what makes this option in some ways useless as it doesn't use data to detect broken connections. However, it is easy to add to your code to see. Add it and see if it helps...

This is how it's done in C or C++

int opt = 1;
// Get the native socket handle from your socket class here...
int socket_handle = getNativeSocketHandle( self.outputStream );

/*Enabling keep alive*/
if( setsockopt( socket_handle, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof( opt ) ) < 0 )
{
   // failed setting socket option
}

The not-so-simple answer is to add a ping packet to your protocol and send that ping packet regularly so you can detect a broken connection.

JimR
  • 15,513
  • 2
  • 20
  • 26
  • Hmm. Is there a way to do this using the API above in Objective-C? – Hahnemann Jul 09 '12 at 01:26
  • @Hahnemann: I do not know Objective-C well, so I do not know. However, I'm willing to bet there's an escape hatch to make calls to C libs. – JimR Jul 09 '12 at 02:57
  • @Hahnemann: Take a look here: http://developer.apple.com/library/ios/DOCUMENTATION/CoreFoundation/Reference/CFStreamConstants/Reference/reference.html#//apple_ref/doc/c_ref/kCFStreamPropertySocketNativeHandle and see if it helps. – JimR Jul 09 '12 at 03:03
  • @JimR : I'm getting this error message : "failed to set keepalive! ERRNO: Socket operation on non-socket" What might be reason and possible solution? – Jayprakash Dubey Oct 13 '14 at 06:50
  • Hi, what is getNativeSocketHandle() – Hardik Mamtora Mar 18 '15 at 07:04
3

You need to routinely send some garbage over the connection. Try this:

NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:240 target:self selector:@selector(timerMethod:) userInfo:[NSNumber numberWithInt:sockfd] repeats:YES];

Then declare this in self:

-(void)timerMethod:(NSTimer*)t
{
  send([[t userInfo] intValue], "Keepalive!", 11, 0);
}

Note: Replace `"Keepalive!" with whatever you want for your protocol. The only requirement is that the remote end (socket server) ignores the message.

tony gil
  • 9,424
  • 6
  • 76
  • 100
Linuxios
  • 34,849
  • 13
  • 91
  • 116
3

I found how to do this. Since the server is running node.js, I used

socket.setKeepAlive(true, 2000);

And in this way node keeps each socket open. The default is false, so this is why it was timing out. I hope this is useful to other people. Thank your for your responses, I think keeping a heartbeat (keep-alive) is also a good idea, but with this native approach node takes care of it.

Hahnemann
  • 4,378
  • 6
  • 40
  • 64
  • you started with ios streams and now you are working with socket object? what kind of api are you using here? – Itay Levin Dec 26 '12 at 16:23
  • 1
    @ItayLevin he's showing you how he did it on the server - see below answer by me for how to do it on iOS. – David H Feb 10 '13 at 23:25
3

Assuming a NSOutputStream *oStream;

On iOS do it this way:

#if 1 // Using CF
CFDataRef data = (CFDataRef)CFWriteStreamCopyProperty((__bridge CFWriteStreamRef)oStream, kCFStreamPropertySocketNativeHandle);
if(data) {
    CFSocketNativeHandle socket_handle = *(CFSocketNativeHandle *)CFDataGetBytePtr(data);
    CFRelease(data);
#else // Using Foundation
NSData *data = (NSData *)[oStream propertyForKey:(__bridge NSString *)kCFStreamPropertySocketNativeHandle];
if(data) {
    CFSocketNativeHandle socket_handle = *(CFSocketNativeHandle *)[data bytes];
#endif
    NSLog(@"SOCK HANDLE: %x", socket_handle);

    /*Enabling keep alive*/
    if( setsockopt( socket_handle, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof( opt ) ) < 0 )
    {
       NSLog(@"Yikes 2: failed to set keepalive! ERRNO: %s", strerror(errno));
    }

I'm posting this since I just spent hours working it all out, want to save someone else the effort.

David H
  • 40,852
  • 12
  • 92
  • 138