NSStream TCP Keep-alive iOS
Asked Answered
I

2

5

I have written this code to setup a stream with a server:

-(void)streamOpenWithIp:(NSString *)ip withPortNumber:(int)portNumber;
{
       CFReadStreamRef readStream;
       CFWriteStreamRef writeStream;
       CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)ip, portNumber, &readStream, &writeStream);

       if(readStream && writeStream)
       {
            CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
            CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);

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

            //Setup outputstream
            outputStream = (__bridge NSOutputStream *)writeStream;
            [outputStream setDelegate:self];
            [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [outputStream open];
       }
}

I am able to connect to the server and send & receive data. But I want to check if the connection is still there. If I disconnect a cable from the wifi router, I still can send data in the stream and no error occurred.

You can solve this on application level by using a timer to send some message and check if you receive something back. But you can solve this also with TCP Keep-Alive technique on a lower level.

How do I implement this with NSStream? How can I set the checking interval?

I assume that you get a NSStreamEventErrorOcurred when the stream is down by checking it with TCP Keep-Alive?

I have checked this post, but I can't figure it out: Keeping a socket connection alive in iOS

Thanks for your help!

Inboard answered 3/4, 2013 at 11:20 Comment(0)
N
12

You can get the native socket handle with

CFDataRef socketData = CFReadStreamCopyProperty((__bridge CFReadStreamRef)(inputStream), kCFStreamPropertySocketNativeHandle);
CFSocketNativeHandle socket;
CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8 *)&socket);
CFRelease(socketData);

and then set socket options (you need to #include <sys/socket.h> for this):

int on = 1;
if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) {
   NSLog(@"setsockopt failed: %s", strerror(errno));
}

You could put this code in the event handler function for the kCFStreamEventOpenCompleted event:

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event {
    switch (event) {
        case kCFStreamEventOpenCompleted:
            if (stream == self.inputStream) {
                CFDataRef socketData = CFReadStreamCopyProperty((__bridge CFReadStreamRef)(stream), kCFStreamPropertySocketNativeHandle);
                CFSocketNativeHandle socket;
                CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8 *)&socket);
                CFRelease(socketData);

                int on = 1;
                if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) {
                    NSLog(@"setsockopt failed: %s", strerror(errno));
                }
            }
            break;

        /* ... other cases ... */;
    }
}
Nablus answered 3/4, 2013 at 12:14 Comment(14)
Can you explain a little bit more what's happening in the code. I don't understand it. I dont have to use NSStream with the delegate methodes anymore?Inboard
@JimCraane: You still use the delegate methods. The first part of my answer shows how to get the native UNIX socket descriptor from the NSInputStream. The second part shows how to set the SO_KEEPALIVE option. I have added a third part that shows how I think you could integrate that in your code. - After setting SO_KEEPALIVE, you should get kCFStreamEventErrorOccurred events if the server is disconnected.Nablus
NSStreamEventOpenCompleted and kCFStreamEventOpenCompleted have both a value of 1, so duplicate case. So I put the code in NSStreamEventOpenCompleted. After that I run the program and make a connection. Than disconnect the UTP Cable from the router. Now I want an error within like 30 seconds or something. What is the standard time interval for TCP Keep-Alive? Can I change that? Sorry for al those questions.Inboard
@JimCraane: NSStreamEventOpenCompleted is fine (my copy/paste error from other sources). - The default timeout is system dependent and I cannot tell you much about it. On Mac OS X, it seems to be 75 seconds ("sudo sysctl net.inet.tcp.keepintvl"). Theoretically, you can configure it system-wide with "sysctl", or on a per-socket basis using "setsockopt", but I have never done this.Nablus
Thanks Martin for this answer. I have the same problem like this. Whenever my app goes in background, my socket goes disconnected and could not be able to continue the connection. I have tried your code , but could not solved my problem. Please help..Charkha
@JimCraane: Is there a reason that you "unaccepted" the answer? Please let me know if there is a problem with it.Nablus
@Martin R: I still didn't get it working! But the project I was working on is not relevant anymore. For other viewers that face the same problem and viewed this post, they need to know that your answer didn't help for me. I can't test it anymore :( Maby I am stupid and don't understand your answer or maby the answer is incomplete. But anyway, I am still happy that you helped me! If someone can approve it, I will make it the correct answer again!Inboard
@JimCraane: OK. Of course you should not accept the answer if it does not work for you. I just wondered that you removed the check mark after so long time. But never mind :-)Nablus
@MartinR : I'm getting "setsockopt failed: Socket operation on non-socket" error. How to resolve this one?Fernand
@JayprakashDubey: What kind of connection did you create? setsockopt() can only be used with (IP) sockets.Nablus
@MartinR : I have used CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)CFBridgingRetain([url host]), port, &readStream, &writeStream); methodFernand
@MartinR : How to go with using CFStreamCreatePairWithSocketToHost(...);Fernand
Can we have the swift 5 version of the snippet?Woolgrower
@AnuragSharma: I'll have a look at it.Nablus
I
0

There is a more complete answer to a similar question.

For the example of the app that starts sending keepalive after 10 seconds, sends keppalive every 2 seconds and closes the stream after 4 keepalives with no reply, take a look at this post: Is it possible to activate TCP keepalive on Apple iOS devices

It also shows how to set retransmission timeout after which the connection is closed.

Intermit answered 15/12, 2015 at 15:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.