I have an application set up to send data between two iOS devices using NSStream
s over a TCP connection.
The data sent consists of two parts:
NSStrings
and an NSData
object encoded with NSKeyedArchiverThe issue:
It seems like the data is getting out of order at some point... though the whole purpose of TCP is to keep it in order. So, I must be the problem!
sendData:
is passed the Message object that has been encoded with an NSKeyedArchiver. It is called for 100ish messages in a short period of time
// dataQueue is an NSMutableArray
- (void) sendData:(NSData *)data
{
int size = data.length;
NSData *msgSize = [NSData dataWithBytes:&size length:sizeof(int)];
if (self.outputStream.hasSpaceAvailable && (self.dataQueue.count == 0)) {
[self.dataQueue addObject:data];
[self.outputStream write:msgSize.bytes maxLength:msgSize.length];
}
else {
[self.dataQueue addObject:msgSize];
[self.dataQueue addObject:data];
}
}
//called by NSStreamDelegate method when space is available
- (void) hasSpaceAvailable
{
if (self.dataQueue.count > 0) {
NSData *tmp = [self.dataQueue objectAtIndex:0];
[self.outputStream write:tmp.bytes maxLength:tmp.length];
[self.dataQueue removeObjectAtIndex:0];
}
}
streamHasBytes:
collects the message fragments and appends them to self.buffer. When the length of self.buffer becomes greater than the message length indicated in the first 4 bytes of self.buffer, the Message object is parsed...
//Called by NSStreamDelegate method when bytes are available
- (void) streamHasBytes:(NSInputStream *)stream
{
NSInteger bytesRead;
uint8_t buffer[32768];
bytesRead= [stream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1 || bytesRead == 0) //...err
@synchronized(self) { //added to test concurrency
[self.buffer appendBytes:buffer length:bytesRead];
}
[self checkForMessage];
}
- (void) checkForMessage
{
@synchronized(self) { //added to test concurrency
int msgLength = *(const int *)self.buffer.bytes;
if (self.buffer.length < msgLength) return;
//remove the integer from self.buffer
[self.buffer replaceBytesInRange:NSMakeRange(0, sizeof(int)) withBytes:NULL length:0];
//copy the actual message from self.buffer
NSData *msgData = [NSData dataWithBytes:self.buffer.bytes length:msgLength];
//remove the message from self.buffer
[self.buffer replaceBytesInRange:NSMakeRange(0, msgLength) withBytes:NULL length:0];
Message *theMsg = [NSKeyedUnarchiver unarchiveObjectWithData:msgData];
[self.delegate didReceiveMessage:theMsg];
}
}
I'm now noticing that, in the case where the NSData object in the first Message is around 1.5Mb, for a total Message size of about 1.6Mb, only about 1.3Mb are received by the client... This would explain the incomprehensible archive errors. Why would all of the data not be delivered?
It turns out that in some cases only a fraction of the data that I assumed was sending was actually sending. NSOutputStream
's write:maxLength:
method returns the number of bytes that were actually written to the stream. So the hasSpaceAvailable
method above can be fixed with
NSInteger i = [self.outputStream write:tmp.bytes maxLength:tmp.length];
if (i < tmp.length) {
//send the difference
}