Jörgen Sigvardsson
Jörgen Sigvardsson

Reputation: 4897

TCP connections with NSStream/CFStream

I am desperately trying to figure out how to detect errors when opening TCP streams using NSStream +getStreamsToHost/CFStreamCreatePairWithSocket(). If I do this:

NSInputStream* input = nil;
NSOutputStream* output = nil;
[NSStream getStreamstoHost:[NSHost hostWithName:@"localhost"] port:80 inputStream:&input outputStream:&output];

NSError* error1 = [input streamError];
assert(error1 == nil);
NSStreamStatus status1 = [input streamStatus];

[input open];
NSError* error2 = [input streamError];
assert(error2 == nil);
NSStreamStatus status2 = [input streamStatus];

status1 is NSStreamStatusNotOpen, which is expected. error1 is nil. error2 is also nil, and status2 is NSStreamStatusOpening. If I telnet to the same address, I get connection refused - there is nothing listening on port 80. If I try to connect to some nonsensical address, such as yaddayadda, I get nil streams.

How do I handle errors properly? No example anywhere seems to handle error conditions, and the docs don't say anything about it, other than the streams may be nil. I'm stumped. Don't tell me I have to run the connection through a run loop, just to get proper error handling...?

I know there's always the possibility of using good ol' BSD sockets, but the docs warns against that, as some high level networking features may fail (auto connections over VPN, and similar stuff).

Upvotes: 1

Views: 8876

Answers (3)

thelastworm
thelastworm

Reputation: 607

I faced the same problem and this is how i fixed it. In my case i used the SSL but you can skip that part of code.

NSString *host = @"10.38.129.234";

CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;

CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)host, 403, &readStream, &writeStream);

[readStream setProperty:NSStreamSocketSecurityLevelSSLv3 forKey:NSStreamSocketSecurityLevelKey];
[readStream setProperty:(id)kCFBooleanFalse forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
[writeStream setProperty:NSStreamSocketSecurityLevelSSLv3 forKey:NSStreamSocketSecurityLevelKey];
[writeStream setProperty:(id)kCFBooleanFalse forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];

//Setup SSL properties
NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
                          [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
                          [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
                          [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
                          @"10.38.129.234",kCFStreamSSLPeerName,
                          nil];
settings = [settings autorelease];

CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);

//Open the OutputStream
CFWriteStreamOpen(writeStream);
UInt8 buf[] = "your message ";
[self writeToStream:writeStream :buf];

//Read the Stream
CFReadStreamOpen(readStream);
NSString *response = [self readFromStream:readStream];
if(response != nil)
{
    NSLog(@"%@",response);
}

UInt8 pull[] = "another message\n";
[self writeToStream:writeStream :pull];

response = [self readFromStream:readStream];
if(response != nil)
{
    NSLog(@"%@",response);
}

//Close the readstream
CFReadStreamClose(readStream);
CFRelease(readStream);
readStream = NULL;

//Close the writestream
CFWriteStreamClose(writeStream);
CFRelease(writeStream);
writeStream = NULL;

Upvotes: 2

owen gerig
owen gerig

Reputation: 6172

read over the stream guide at apple LINK

you will see you are suppose to have a method that switches stream events like so

 - (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {

    switch (streamEvent) 
    {
                case NSStreamEventOpenCompleted:
        {
            DDLogVerbose(@"Stream opened");
                        break;
        }

        case NSStreamEventHasBytesAvailable:
        {
            if(!rawData) {
                rawData = [[NSMutableData data] retain];
            }
            uint8_t buf[1024];
            unsigned int len = 0;
            len = [(NSInputStream *)theStream read:buf maxLength:1024];
            if(len) {
                [rawData initWithBytes:buf length:len];                
            } else {
                DDLogVerbose(@"no buffer!");
            }
            const uint8_t *bytes = [rawData bytes];
            NSMutableArray *mutableBuffer = [[NSMutableArray alloc] initWithCapacity:len];  

            for (int i =0; i < [rawData length]; i++) {
                [mutableBuffer addObject:[NSString stringWithFormat:@"%02X", bytes[i]]];
            }
            [self gateKeeper:mutableBuffer];
            [mutableBuffer release];
            break;
        }       
                case NSStreamEventErrorOccurred:
        {
            if ([theStream isKindOfClass:[NSInputStream class]]) {
                NSString* address = [self getAddress];
                NSString* myIPAdress = [NSString stringWithFormat:@"IP Address: %@", address];

                //[cClient updateRequest];
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Cant Connect" message:[NSString stringWithFormat:@"Cant connect to server: %@, Make sure you are connected to the proper wireless network.  Your Ip Address is %@",CCV.ipAddress,myIPAdress] delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:@"Reconnect", nil];

                [alert show];
                [alert release];                        
            }
            break;   
        }

                case NSStreamEventEndEncountered:
        {
            [theStream close];
            [theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [theStream release];
            break;
        }
        case NSStreamEventHasSpaceAvailable:
        {
            //DDLogVerbose(@"has space available");
            break;
        }

        case NSStreamEventNone:
        {
            DDLogVerbose(@"none");
            break;
        }

                default:
        {
                        DDLogVerbose(@"Unknown event");
        }
        }
}

Upvotes: 1

J&#246;rgen Sigvardsson
J&#246;rgen Sigvardsson

Reputation: 4897

What you need to do is either poll for the status using the streamStatus method on the input stream, or schedule the streams for events in a run loop. To better provide error information about erroneous host names, it's better to resolve the name before hand using either NSHost or CFHost.

Upvotes: 0

Related Questions