1

I'm developing an iOS app (using Swift) which uses a TCP connection to a TCP server. Currently, whenever I've sent something, the connection closes automatically. I want to keep the connection open/alive until I manually close it.

From this Objective-C-based question I found that it could be done like this in Objective-C:

#include <sys/socket.h>
...
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));
}

My current Swift implementation/translation looks like this:

var socketData = CFReadStreamCopyProperty(inputStream, kCFStreamPropertySocketNativeHandle) as CFDataRef
var socket: CFSocketNativeHandle
CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8).self&socket)

var on: UInt8 = 1;
if setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, 255) == -1 {
}

A few notes:

  • inputStream is declared as: var inputStream: NSInputStream?
  • I'm not sure if using 255 as an alternative to sizeof(on) is a good idea.
  • The code happens in the function func stream(theStream:NSStream, handleEvent streamEvent:NSStreamEvent) which is required by using the NSStreamDelegate protocol.
  • I'm not sure if using inputStream instead of theStream (function parameter) is a good idea.

I'm getting an Xcode error on the CFDataGetBytes function. It says the following:

'NSData' is not a subtype of of 'CFData'

Any idea how to fix that?

Also, how do I import/include <sys/socket.h> in my Swift file? I've seen something called bridging headers, but isn't that only for Obj-C side-by-side with Swift implementations?

Community
  • 1
  • 1
gosr
  • 4,593
  • 9
  • 46
  • 82
  • TCP keep-alive is to detect errors on idle connections. It does not help against "automatic closing". The latter is an application problem and to help here one would need to see your code. – Steffen Ullrich Sep 03 '14 at 02:52

1 Answers1

2

CFDataRef is "toll-free bridged" to NSData, therefore the first part can be more simply written as

var socketData = CFReadStreamCopyProperty(inputStream, kCFStreamPropertySocketNativeHandle) as NSData
var socket: CFSocketNativeHandle = 0
socketData.getBytes(&socket, length: sizeofValue(socket))

The "option_value" argument of setsockopt() is the address of an int (which is mapped to Swift as UInt32), and the last argument has to be the size of that variable:

var on: UInt32 = 1;
if setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, socklen_t(sizeofValue(on))) == -1 {
    let errmsg = String.fromCString(strerror(errno))
    println("setsockopt failed: \(errno) \(errmsg)")
}

<sys/socket.h> is imported by default.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks for the answer. Do you happen to know where to look if I get `setsockopt failed: 0x00007ff369887200`? That is, the `setsockopt` function returns -1. – gosr Sep 03 '14 at 14:25
  • @eightx2: I have updated the code to show a proper error number and message in the failure case. - The possible errnos are listed in the man pages ("man setsockopt" in the Terminal, or https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setsockopt.2.html). – Martin R Sep 03 '14 at 14:31
  • @eightx2: Did you make any progress? – Martin R Sep 06 '14 at 07:46
  • Thanks for the update. I got this error: `setsockopt failed: 22 Invalid argument`. I learned that I had to just write `8` instead of `socklen_t(sizeofValue(on))`. – gosr Sep 07 '14 at 14:34
  • @eightx2: That means that `UInt8` is not the right data type for the socket option. The C `int` maps to `Int32`, so you probably have to use `var on: UInt32 = 1` (together with `socklen_t(sizeofValue(on))`). – Martin R Sep 07 '14 at 14:44