Question

I am making an app with two components, an iPhone component and a Mac component. They are supposed to communicate with each other via bonjour. I use the following code on the Mac end to find a port for the service:

NSSocketPort *socket = [[NSSocketPort alloc] init];
struct sockaddr *addr = (struct sockaddr *)[[socket address] bytes];
int port = 9876;
if(addr->sa_family == AF_INET) {
    port = ntohs(((struct sockaddr_in *)addr)->sin_port);
} else if(addr->sa_family == AF_INET6) {
    port = ntohs(((struct sockaddr_in6 *)addr)->sin6_port);
} else {
    [socket release];
    socket = nil;
    NSLog(@"The family is neither IPv4 nor IPv6. Can't handle!!!");
}

I can also use this code on the iPhone end and run the app in the simulator, and the connection works fine. However, when I tried to run this code on an actual iPhone, I discovered that NSSocketPort is not available on the iPhone. When I try to start the service with the port 9876, the Mac app displays a connection refused error when I try to connect with it. So how can I find a port to use without using NSSocketPort?

Was it helpful?

Solution

Ok I looked at Apple's WiTap code and slightly modified it to write myself a method for getting a port:

- (int) getPort {
CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};   

// Start by trying to do everything with IPv6.  This will work for both IPv4 and IPv6 clients 
// via the miracle of mapped IPv4 addresses.    

CFSocketRef witap_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET6, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, nil, &socketCtxt);
uint32_t protocolFamily;

if (witap_socket != NULL)   // the socket was created successfully
{
    protocolFamily = PF_INET6;
} else // there was an error creating the IPv6 socket - could be running under iOS 3.x
{
    witap_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, nil, &socketCtxt);
    if (witap_socket != NULL)
    {
        protocolFamily = PF_INET;
    }
}

/*if (NULL == witap_socket) {
    if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerNoSocketsAvailable userInfo:nil];
    if (witap_socket) CFRelease(witap_socket);
    witap_socket = NULL;
    return NO;
}*/


int yes = 1;
setsockopt(CFSocketGetNative(witap_socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));

// set up the IP endpoint; use port 0, so the kernel will choose an arbitrary port for us, which will be advertised using Bonjour
if (protocolFamily == PF_INET6)
{
    struct sockaddr_in6 addr6;
    memset(&addr6, 0, sizeof(addr6));
    addr6.sin6_len = sizeof(addr6);
    addr6.sin6_family = AF_INET6;
    addr6.sin6_port = 0;
    addr6.sin6_flowinfo = 0;
    addr6.sin6_addr = in6addr_any;
    NSData *address6 = [NSData dataWithBytes:&addr6 length:sizeof(addr6)];

    CFSocketSetAddress(witap_socket, (CFDataRef)address6);
    /*if (kCFSocketSuccess != CFSocketSetAddress(witap_socket, (CFDataRef)address6)) {
        if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerCouldNotBindToIPv6Address userInfo:nil];
        if (witap_socket) CFRelease(witap_socket);
        witap_socket = NULL;
        return NO;
    }*/

    // now that the binding was successful, we get the port number 
    // -- we will need it for the NSNetService
    NSData *addr = [(NSData *)CFSocketCopyAddress(witap_socket) autorelease];
    memcpy(&addr6, [addr bytes], [addr length]);
    return ntohs(addr6.sin6_port);

} else {
    struct sockaddr_in addr4;
    memset(&addr4, 0, sizeof(addr4));
    addr4.sin_len = sizeof(addr4);
    addr4.sin_family = AF_INET;
    addr4.sin_port = 0;
    addr4.sin_addr.s_addr = htonl(INADDR_ANY);
    NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)];

    CFSocketSetAddress(witap_socket, (CFDataRef)address4);
    /*if (kCFSocketSuccess != CFSocketSetAddress(witap_socket, (CFDataRef)address4)) {
        if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerCouldNotBindToIPv4Address userInfo:nil];
        if (witap_socket) CFRelease(witap_socket);
        witap_socket = NULL;
        return NO;
    }*/

    // now that the binding was successful, we get the port number 
    // -- we will need it for the NSNetService
    NSData *addr = [(NSData *)CFSocketCopyAddress(witap_socket) autorelease];
    memcpy(&addr4, [addr bytes], [addr length]);
    return ntohs(addr4.sin_port);
}

}

I took out a lot of the error stuff so it's probably not optimal, but it works.

OTHER TIPS

As of iOS 7, NSNetService can create and bind the socket for you. If initialized with port zero, it will choose a random port:

NSNetService *service = [[NSNetService alloc] initWithDomain:@"local." type:@"_test._tcp." name:@"Test Service" port:0];
[service publishWithOptions:NSNetServiceListenForConnections];

The service will send netService:didAcceptConnectionWithInputStream:outputStream: to its delegate when a connection is established.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top