问题
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:
- An integer indicating the size of the message object to come
- The message object, some
NSStrings
and anNSData
object encoded with NSKeyedArchiver
The issue:
- When the NSData object is around 1.5Mb, I get an incomprehensible archive exception when I try to decode it. When reading the 4 bytes following where the message should be, a large number is there, not the size of the next message.
- When the NSData object is around 80Kb, two messages are successfully decoded and then I get the incomprehensible archive exception.
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!
Relevant Code
Server
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];
}
}
Client
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];
}
}
EDIT:
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?
回答1:
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
}
回答2:
I am doing something practically the same, but I went with a little different method. I chose to get the size of the archived dataToSend, then convert the int sized variable to a mutableData chunk. Then I append the dataToSend to the dataSized chunk, creating one chunk of data that you can handle and is "concatenated" to send together. That way everything stays together. Things are supposed to stay together, and they do. You'll have to adjust the buffers accordingly. I have data around 400 to 450 bytes so I have a 512 byte buffer for the dataToSend, then the server and clients have 4096 (4K) buffer to accumulate data sent. But it never goes past 1536 bytes with 4 players sharing data at 30 fps...
Each Player Calls:
- (void)sendData:(CAKNodeSpec *)sentSpec {
NSMutableData *archivedData = [NSMutableData dataWithCapacity:512];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archivedData];
[sentSpec encodeWithCoder:archiver];
[archiver finishEncoding];
[self.gameClient outputData:archivedData];
// gameClient is custom NSStreamDelegate with IN/OUT Streams
}
In gameClient, and gameConnect for gameServer
- (void)outputData:(NSMutableData *)dataToSend {
if (self.outputBuffer != nil) {
unsigned int size = (unsigned int)dataToSend.length;
// maintains 32-bit/64-bit architecture compatibility & silence warnings
NSMutableData *dataSized = [NSMutableData dataWithBytes:&size length:sizeof(unsigned int)];
[dataSized appendBytes:dataToSend.bytes length:size];
[self.outputBuffer appendData:dataSized];
[self startOutput];
}
}
- (void)startOutput {
NSInteger actuallyWritten = [self.outputStream write:self.outputBuffer.bytes maxLength:self.outputBuffer.length];
if (actuallyWritten > 0) {
[self.outputBuffer replaceBytesInRange:NSMakeRange(0, (NSUInteger) actuallyWritten) withBytes:NULL length:0];
} else {
[self closeStreams];
}
}
following the classic switch-case-model
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)streamEvent {
assert(aStream == self.inputStream || aStream == self.outputStream);
switch(streamEvent) {
case NSStreamEventOpenCompleted: {
if (aStream == self.inputStream) {
self.inputBuffer = [[NSMutableData alloc] init];
self.dataQueue = [[NSMutableArray alloc] init];
} else {
self.outputBuffer = [[NSMutableData alloc] init];
}
} break;
case NSStreamEventHasSpaceAvailable: {
if ([self.outputBuffer length] != 0) {
[self startOutput];
}
} break;
case NSStreamEventHasBytesAvailable: {
uint8_t buffer[4096];
NSInteger actuallyRead = [self.inputStream read:buffer maxLength:4096];
if (actuallyRead > 0) {
[self.inputBuffer appendBytes:buffer length:(NSUInteger)actuallyRead];
NSLog(@"Read: %ld", (long)actuallyRead);
[self chopaChunk];
NSMutableData *specData;
while ((specData = [self.dataQueue shift])) {
[self.delegate handleData:specData]; // unarchives
}
} else {
NSLog(@"empty buffer!");
}
} break;
case NSStreamEventErrorOccurred:
case NSStreamEventEndEncountered: {
[self closeStreams];
} break;
default:
break;
}
}
This is the real difference area:
- (void)chopaChunk {
unsigned int dataSize = *(const unsigned int *)self.inputBuffer.bytes;
while (self.inputBuffer.length >= (sizeof(unsigned int) + dataSize)) {
//remove the integer from self.inputBuffer
[self.inputBuffer replaceBytesInRange:NSMakeRange(0, sizeof(unsigned int)) withBytes:NULL length:0];
//copy the actual message from self.inputBuffer
NSMutableData *specData = [NSMutableData dataWithBytes:self.inputBuffer.bytes length:dataSize];
[self.dataQueue addObject:specData];
//remove the message from self.inputBuffer
[self.inputBuffer replaceBytesInRange:NSMakeRange(0, dataSize) withBytes:NULL length:0];
if (self.inputBuffer.length > 0) {
dataSize = *(const unsigned int *)self.inputBuffer.bytes;
// I just keep going adding, I guess you would add multiple 80K pieces here
}
}
}
Following your lead, I choose to check if the inputBuffer is >= dataSize + (sizeof(unsigned int)), that way like a sausage maker we just twist off pieces as they come in! :D
The flow is data to outputBuffer to outputStream <- send -> to inputStream to inputBuffer back to data.
I think if you goto using the concatenated data instead of trying to send two different objects you'll feel better than trying to figure out who's who and when you get what... and >= instead of <, and just add mutableData blocks to your dataQueue...your use case(s) may differ...
来源:https://stackoverflow.com/questions/11528977/parsing-data-after-sending-between-ios-devices-over-nsstream