PDA

View Full Version : Can't use NSHost - so what's the best way to change this?




LastLine
Aug 10, 2010, 11:48 AM
Ok so I submitted an app using NSHost, didn't notice that it was a non-public API, my bad.

What I have is this.


NSHost *host = [NSHost hostWithAddress:hostname];
if (host == nil) {
host = [NSHost hostWithName:hostname];
if (host == nil) {
[self setMessage:@"Invalid IP address or hostname:"];
return;
}
}



I have found alternatives that work when receiving WithName (i.e. a Bonjour advertised service) what I can't do at the minute is work out how to deal with hostWithAddress (necessary as my users can manually enter an IP address to connect to a Windows client)
Does anyone have any great ideas on how I might work around this?

Currently have this
struct hostent *remoteHostEnt = gethostbyname(hostname);
struct in_addr *remoteInAddr = (struct in_addr *) remoteHostEnt->h_addr_list[0];
char *sRemoteInAddr = inet_ntoa(*remoteInAddr);
NSString *host = [[NSString alloc] initWithFormat:@"Remote IP: %s\n", sRemoteInAddr];
in place which works great for advertised Bonjour services, but not for manually entered IP Addresses.



pflau
Aug 11, 2010, 12:46 AM
Mind telling plainly what is it that you want to do?

LastLine
Aug 11, 2010, 03:20 AM
Mind telling plainly what is it that you want to do?

Certainly. Here's a bit more code to help with the explanation too.


- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *serverString = [[textField text] stringByReplacingCharactersInRange:range withString:string];
[[NSUserDefaults standardUserDefaults] setObject:serverString forKey:kDefaultKeyServerName];
return YES;
}

- (BOOL)textFieldShouldClear:(UITextField *)textField {
[[NSUserDefaults standardUserDefaults] setObject:@"" forKey:kDefaultKeyServerName];
return YES;
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
NSMutableString *serverString = [NSMutableString stringWithString:[textField text]];
if ([serverString replaceOccurrencesOfString:@" " withString:@"" options:0 range:NSMakeRange(0, [serverString length])]) {
[[NSUserDefaults standardUserDefaults] setObject:serverString forKey:kDefaultKeyServerName];
[textField setText:serverString];
}
if ([serverString length] == 0) {
[self setMessage:@""];
return;
}

NSRange separator = [serverString rangeOfString:@":" options:NSBackwardsSearch];
NSInteger port;
NSString *hostname;
if (separator.location != NSNotFound) {
hostname = [serverString substringToIndex:separator.location];
port = [[serverString substringFromIndex:separator.location + separator.length] integerValue];
if (port <= 0 || 65536 <= port) {
[self setMessage:@"Invalid port number:"];
return;
}
} else {
hostname = serverString;
port = kDefaultPort;
}


Boolean result;
CFHostRef hostRef;
CFArrayRef addresses;

hostRef = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)hostname);
if (hostRef) {
result = CFHostStartInfoResolution(hostRef, kCFHostAddresses, NULL); // pass an error instead of NULL here to find out why it failed
if (result == TRUE) {
addresses = CFHostGetAddressing(hostRef, &result);
}
}
if (result == TRUE) {
NSLog(@"Resolved");
} else {
NSLog(@"Not resolved");
}

NSInputStream *inputStream;
NSOutputStream *outputStream;
[NSStream getStreamsToHost:hostRef port:port inputStream:&inputStream outputStream:&outputStream];
if (inputStream == nil || outputStream == nil) {
[self setMessage:@"Cannot connect:"];
} else {
[self setMessage:@""];
[(AppController *)[UIApplication sharedApplication].delegate setInputStream:inputStream outputStream:outputStream];
}
}

Basically what I'm doing is looking at two possible input sources for an IP address.

Number one is a table view display compatible locally advertised Bonjour services. This works well and has no problems. If you tap on one the program resolves it and connects happily.

Number two is that the user can manually enter the IP address, this is done as I can't for the life of me find a way (simple or otherwise) to get Windows to advertise a Bonjour service in a VC++ app, now using
NSHost *host = [NSHost hostWithAddress:hostname]; this worked well, it took the string and well...changed it to an IPv4 address quite contentedly which then connected. The trouble is I can't quite figure out how to get the same response from CFHost (which I now have implemented for Bonjour services as you might notice above instead of the old depreceated C code mentioned in my original post.

Sorry for wall'o'text hopefully that helps explain things though,

pflau
Aug 11, 2010, 08:27 AM
Why not just bypass the foundation class and use the Unix services getaddrinfo and gethostbyaddr?

LastLine
Aug 11, 2010, 10:58 AM
Why not just bypass the foundation class and use the Unix services getaddrinfo and gethostbyaddr?

What are the advantages? I've not really tried that before. Say I've got the string serverString from my text field which contains a string formatted as an IP address. What would I do to make that useful?

xStep
Aug 11, 2010, 12:50 PM
Number two is that the user can manually enter the IP address, this is done as I can't for the life of me find a way (simple or otherwise) to get Windows to advertise a Bonjour service in a VC++ app

Have you looked at the Bonjour Windows SDK available from Apple? I found it in the OS X downloads section under the Developer Tools tab/section.

pflau
Aug 11, 2010, 12:55 PM
Use inet_aton to translate the address string to a structure, and pass that structure to gethostbyaddr to get a structure that contains the hostname and everything.

LastLine
Aug 11, 2010, 12:56 PM
Have you looked at the Bonjour Windows SDK available from Apple? I found it in the OS X downloads section under the Developer Tools tab/section.

I have as it happens. I forget what my reasoning for not using it was but it wasn't suitable for some reason, I think it fell to the fact I had NSHost working at the time with the IP input and didn't want to rewrite my networking code from scratch.

EDIT: Just looked at it again, and to be honest it confused me more than this current method is and given the style of my app provides little additional benefit to the end user.

Use inet_aton to translate the address string to a structure, and pass that structure to gethostbyaddr to get a structure that contains the hostname and everything.
Sorry, this must have come in while I was replying, didn't mean to ignore it, I've looked into it a bit further and you're quite possibly on to something, that said it's got my head running around in circles right now. I've looked at some sample code snippets and tried to incorporate them but I seem to be failing miserably, let me see if I can demonstrate what I'm doing without posting a whole implementation file!


- (void)textFieldDidEndEditing:(UITextField *)textField {
NSMutableString *serverString = [NSMutableString stringWithString:[textField text]];
if ([serverString replaceOccurrencesOfString:@" " withString:@"" options:0 range:NSMakeRange(0, [serverString length])]) {
[[NSUserDefaults standardUserDefaults] setObject:serverString forKey:kDefaultKeyServerName];
[textField setText:serverString];
//Now I figure this is where I would try and use inet_aton()
struct sockaddr_in antelope;
inet_aton(serverString, &antelope.sin_addr);
//Which to my understanding stores it in a structure called antelope (antelope because it was in the reference I found and it amused me ;-) I'm just not sure where to go from there? I get a warning on the inet_aton line telling me "Passing argument 1 of 'inet_aton' from incompatible pointer type"
}
if ([serverString length] == 0) {
[self setMessage:@""];
return;
}

NSRange separator = [serverString rangeOfString:@":" options:NSBackwardsSearch];
NSInteger port;
NSString *hostname;
if (separator.location != NSNotFound) {
hostname = [serverString substringToIndex:separator.location];
port = [[serverString substringFromIndex:separator.location + separator.length] integerValue];
if (port <= 0 || 65536 <= port) {
[self setMessage:@"Invalid port number:"];
return;
}
} else {
hostname = serverString;
port = kDefaultPort;
}

/*// Works fine for Bonjour Services
struct hostent *remoteHostEnt = gethostbyname(hostname);
struct in_addr *remoteInAddr = (struct in_addr *) remoteHostEnt->h_addr_list[0];
char *sRemoteInAddr = inet_ntoa(*remoteInAddr);
NSString *host = [[NSString alloc] initWithFormat:@"Remote IP: %s\n", sRemoteInAddr];
*/
Boolean result;
CFHostRef hostRef;
CFArrayRef addresses;

hostRef = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)hostname);
if (hostRef) {
result = CFHostStartInfoResolution(hostRef, kCFHostAddresses, NULL); // pass an error instead of NULL here to find out why it failed
if (result == TRUE) {
addresses = CFHostGetAddressing(hostRef, &result);
}
}
if (result == TRUE) {
NSLog(@"Resolved");
} else {
NSLog(@"Not resolved");
}

NSInputStream *inputStream;
NSOutputStream *outputStream;
[NSStream getStreamsToHost:hostRef port:port inputStream:&inputStream outputStream:&outputStream];
if (inputStream == nil || outputStream == nil) {
[self setMessage:@"Cannot connect:"];
} else {
[self setMessage:@""];
[(AppController *)[UIApplication sharedApplication].delegate setInputStream:inputStream outputStream:outputStream];
}
}