I tried to write a Client(iPad)/Server(iMac) application based on the CocoaEcho example. My first simple example worked, but after adding more functionality the client is unable to find the server.
After starting the server, I start the client, both in a local network. The client starts searching for services and gets a "netServiceBrowserWillSearch:" message for its browser, but after that nothing happens. Triggering the search for services again, results in a "didNotsearch:" message with error -72003, 10 (browser is still busy searching).
1) I checked that the server is reachable with the WiTap app. There client and server connect correctly.
2) I checked if the server publishes the service with "dns-sd -B _cocoaecho", it is detected.
3) The nsnetservicebrowser object in the client app is declared a property, so there should not be a scope problem. I also checked in the debugger, it is still there....
My Code:
Client:
@interface MySocketClient : UIResponder <NSNetServiceBrowserDelegate, NSStreamDelegate>
{
...
NSNetService * myServer;
NSString* nextMsg;
}
@property (nonatomic, strong, readwrite) NSMutableArray * services; // of NSNetService
@property (nonatomic, strong, readwrite) NSNetServiceBrowser * serviceBrowser;
@property (nonatomic, strong, readwrite) NSInputStream * inputStream;
@property (nonatomic, strong, readwrite) NSOutputStream * outputStream;
@property (nonatomic, strong, readwrite) NSMutableData * inputBuffer;
@property (nonatomic, strong, readwrite) NSMutableData * outputBuffer;
....
-(void) setup{
...
self.serviceBrowser = [[NSNetServiceBrowser alloc] init];
self.services = [[NSMutableArray alloc] init];
[self.serviceBrowser setDelegate:self];
[self.serviceBrowser searchForServicesOfType:@"_cocoaecho._tcp." inDomain:@"local."];
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didNotSearch:(NSDictionary *)errorInfo
{
NSLog(@"%@", errorInfo);
}
// Sent when browsing begins
- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)browser
{
NSLog(@"will search \n");
}
// Sent when browsing stops
- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser
{
NSLog(@"stopped search \n");
}
//We broadcast the willChangeValueForKey: and didChangeValueForKey: for the NSTableView binding to work.
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
#pragma unused(aNetServiceBrowser)
#pragma unused(moreComing)
NSLog(@"found a service \n");
if (![self.services containsObject:aNetService]) {
[self willChangeValueForKey:@"services"];
[self.services addObject:aNetService];
[self didChangeValueForKey:@"services"];
myServer = aNetService;
}
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
#pragma unused(aNetServiceBrowser)
#pragma unused(moreComing)
if ([self.services containsObject:aNetService]) {
[self willChangeValueForKey:@"services"];
[self.services removeObject:aNetService];
[self didChangeValueForKey:@"services"];
}
}
And the Server:
- (BOOL)start {
assert(_ipv4socket == NULL && _ipv6socket == NULL); // don't call -start twice!
CFSocketContext socketCtxt = {0, (__bridge void *) self, NULL, NULL, NULL};
_ipv4socket = CFSocketCreate(kCFAllocatorDefault, AF_INET, SOCK_STREAM, 0, kCFSocketAcceptCallBack, &EchoServerAcceptCallBack, &socketCtxt);
_ipv6socket = CFSocketCreate(kCFAllocatorDefault, AF_INET6, SOCK_STREAM, 0, kCFSocketAcceptCallBack, &EchoServerAcceptCallBack, &socketCtxt);
if (NULL == _ipv4socket || NULL == _ipv6socket) {
[self stop];
return NO;
}
static const int yes = 1;
(void) setsockopt(CFSocketGetNative(_ipv4socket), SOL_SOCKET, SO_REUSEADDR, (const void *) &yes, sizeof(yes));
(void) setsockopt(CFSocketGetNative(_ipv6socket), SOL_SOCKET, SO_REUSEADDR, (const void *) &yes, sizeof(yes));
// Set up the IPv4 listening socket; port is 0, which will cause the kernel to choose a port for us.
struct sockaddr_in addr4;
memset(&addr4, 0, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
addr4.sin_port = htons(0);
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
if (kCFSocketSuccess != CFSocketSetAddress(_ipv4socket, (__bridge CFDataRef) [NSData dataWithBytes:&addr4 length:sizeof(addr4)])) {
[self stop];
return NO;
}
// Now that the IPv4 binding was successful, we get the port number
// -- we will need it for the IPv6 listening socket and for the NSNetService.
NSData *addr = (__bridge_transfer NSData *)CFSocketCopyAddress(_ipv4socket);
assert([addr length] == sizeof(struct sockaddr_in));
self.port = ntohs(((const struct sockaddr_in *)[addr bytes])->sin_port);
// Set up the IPv6 listening socket.
struct sockaddr_in6 addr6;
memset(&addr6, 0, sizeof(addr6));
addr6.sin6_len = sizeof(addr6);
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(self.port);
memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
if (kCFSocketSuccess != CFSocketSetAddress(_ipv6socket, (__bridge CFDataRef) [NSData dataWithBytes:&addr6 length:sizeof(addr6)])) {
[self stop];
return NO;
}
// Set up the run loop sources for the sockets.
CFRunLoopSourceRef source4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _ipv4socket, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source4, kCFRunLoopCommonModes);
CFRelease(source4);
CFRunLoopSourceRef source6 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _ipv6socket, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source6, kCFRunLoopCommonModes);
CFRelease(source6);
assert(self.port > 0 && self.port < 65536);
self.netService = [[NSNetService alloc] initWithDomain:@"local." type:@"_cocoaecho._tcp." name:@"" port:(int) self.port];
[self.netService publishWithOptions:0];
return YES;
}
I had similar problem. My code successfully registered NSNetService and launched NSNetServiceBrowser but could not -resolveWithTimeout other devices. Strange, but sometimes did work, sometimes not and sometimes worked asymmetrically.
After intense debugging I can give you some tips to check:
Install Bonjour Browser on desktop. Plug out your network cable and check if you are connected to the same WiFi hotspot as mobile devices. Here you should see the same service state as mobile devices will do.
Try with different WiFi hotspot. Strange but my main WiFi performed badly. After I switched to another one it worked like a charm using the very same code. Try unplug WiFi from Internet for testing.
You can add some retains (or assign to static variable) to objects returned from API (like NSNetService). ARC can do silently dealloc if it decides object is not needed anymore. That helped my for some tests.
I was just getting that -72003 error all the time unless I disconnected and reconnected again (even the first time). Which lead to this solution:
I don't know why this works but I'm no longer getting the error.