NSStream sockets missing data
- by Chris T.
I am trying to pull some sample data from FreeDB as a proof of concept, but I am having a tough time retrieving all of the data off the incoming stream (I am only getting the last bits for the final query listed here (if handshakeCode = 3)
I think this may be something with the threading on the main runloop, but I am not sure. Odd thing is when the buffer size is larger than 1-2 bytes (which works as expected), I seem to be losing access to the data programmatically (the totalOutput variable on the first set of data is incomplete). I set up a packet capture, and it looks like those 1024 bytes are coming across the wire, but the app just isn't working with it. It looks like the next event is coming through and basically taking over. I tried using an NSLock to no avail as well. If I drop the buffer size down to 1 or 2, things seem to be reading just fine.
This is probably obvious to someone who does this all the time, but this is my first foray into this with something I am familiar with, technology wise in other languages / platforms.
The following code will show you what is happening. Run with the buffer set to 1024, and you will see a short final string, but once you set it to 1, you will see the amount of data I was expecting (I was even expecting it to be split, so that's not a big worry)
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
//STACK OVERFLOW CODE:
@interface stackoverflow : NSObject <NSStreamDelegate>
{
NSInputStream *iStream;
NSOutputStream *oStream;
int handshakeCode;
NSString *selectedDiscId;
NSString *selectedGenre;
}
-(void)getMatchesFromFreeDB;
-(void)sendToOutputStream:(NSString*)command;
@end
@implementation stackoverflow
-(void)getMatchesFromFreeDB
{
NSHost *host = [NSHost hostWithName:@"freedb.freedb.org"];
[NSStream getStreamsToHost:host
port:8880
inputStream:&iStream
outputStream:&oStream];
[iStream retain];
[oStream retain];
[iStream setDelegate:self];
[oStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[oStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[iStream open];
[oStream open];
handshakeCode = 0; //not done any processing
}
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch(eventCode)
{
case NSStreamEventOpenCompleted:
{
NSLog(@"Stream open completed");
break;
}
case NSStreamEventHasBytesAvailable:
{
NSLog(@"Stream has bytes available");
if (aStream == iStream)
{
NSMutableString *totalOutput = [NSMutableString stringWithString:@""];
//read data
uint8_t buffer[1024];
int len;
while ([iStream hasBytesAvailable])
{
len = [iStream read:buffer maxLength:sizeof(buffer)];
if (len 0)
{
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSUTF8StringEncoding];
//this could have also been put into an NSData object
if (nil != output)
{
//append to the total output
[totalOutput appendString:output];
}
}
}
NSLog(@"OUTPUT , %i:\n\n%@", [totalOutput lengthOfBytesUsingEncoding:NSUTF8StringEncoding], totalOutput);
NSArray *outputComponents = [totalOutput componentsSeparatedByString:@" "];
//Attempt to get handshake code, since we haven't done it yet:
if (handshakeCode == 1)
{
//we are just getting the sign-on banner:
//let's move on:
handshakeCode = 2;
}
else if (handshakeCode == 2)
{
handshakeCode = [[outputComponents objectAtIndex:0] intValue];
if (handshakeCode == 200)
{
NSLog(@"---Handshake OK %i", handshakeCode);
NSMutableString *query = [NSMutableString stringWithString:@"cddb query f3114b11 17 225 19915 36489 54850 69425 87025 103948 123242 136075 152817 178335 192850 211677 235104 262090 284882 308658 4430\n"];
handshakeCode = 3;
[self sendToOutputStream:query];
}
}
else if (handshakeCode == 3)
{
//now, we are reading out the matches:
if ([[outputComponents objectAtIndex:0] intValue] == 200) //found exact match:
{
NSLog(@"Found exact match");
selectedGenre = [outputComponents objectAtIndex:1] ;
selectedDiscId = [outputComponents objectAtIndex:2];
if (selectedGenre && selectedDiscId)
{
//send off the request to get the entry:
NSString *query = [NSString
stringWithFormat:@"cddb read %@ %@\n",
selectedGenre, selectedDiscId];
[self sendToOutputStream:query];
handshakeCode = 4;
}
}
}
}
break;
}
case NSStreamEventEndEncountered:
{
NSLog(@"Stream event end encountered");
break;
}
case NSStreamEventErrorOccurred:
{
NSLog(@"Stream error occurred");
break;
}
case NSStreamEventHasSpaceAvailable:
{
NSLog(@"Stream has space available");
if (aStream == oStream)
{
if (handshakeCode == 0)
{
handshakeCode = 1;
[self sendToOutputStream:@"cddb hello stackoverflow localhost.localdomain test .01BETA\n"];
}
}
break;
}
}
}
-(void)sendToOutputStream:(NSString*)command
{
const uint8_t *rawCommand = (const uint8_t *)[command UTF8String];
[oStream write:rawCommand maxLength:strlen(rawCommand)];
NSLog(@"Sent command: %@",command);
}
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
stackoverflow *test = [[stackoverflow alloc] init];
[test getMatchesFromFreeDB];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
[pool drain];
return 0;
}
Any help is much appreciated!
Thanks