Send and receive NSData via GameKit

I am trying to send multiple NSData over Bluetooth via GameKit .

As long as I have GameKit and I can send small messages, now I would like to expand and send through entire files.

I read that you need to split large files into packages before sending them individually.

So, I decided to create a struct to make it easier to decode packets when they are received at the other end:

 typedef struct { const char *fileName; NSData *contents; int fileType; int packetnumber; int totalpackets; } file_packet; 

However, for small files (8 KB or less), I thought that one package would be enough.

So, for one package, I thought I could create a package_file, set its properties and send it through -sendDataToAllPeers: withDataMode: error:

 NSData *fileData; file_packet *packet = (file_packet *)malloc(sizeof(file_packet)); packet->fileName = [filename cStringUsingEncoding:NSASCIIStringEncoding]; packet->contents = [NSData dataWithContentsOfFile:selectedFilePath]; packet->packetnumber = 1; packet->totalpackets = 1; packet->fileType = 56; //txt document fileData = [NSData dataWithBytes:(const void *)packet length:sizeof(file_packet)]; free(packet); NSError *error = nil; [self.connectionSession sendDataToAllPeers:fileData withDataMode:GKSendDataReliable error:&error]; if (error) { NSLog(@"An error occurred: %@", [error localizedDescription]); } 

However, I do not think that something is setting fileData correctly - and error does not display anything.

When the file is received, I do the following:

 file_packet *recievedPacket = (file_packet *)malloc(sizeof(file_packet)); recievedPacket = (file_packet *)[data bytes]; NSLog(@"packetNumber = %d", recievedPacket->packetnumber); ... 

However, the console packetNumber = 0 , even when I set the packet number to 1.

Am I missing the obvious? I don't know much about NSData or GameKit .

So my question is: can I add file_packet to NSData , and if so, how to do it successfully - and how do you split files into multiple packages?

+5
source share
2 answers

Add:

What you have to do here is to subclass NSObject to represent your package, and then accept NSCoding to serialize it to NSData the way you want. Doing this with a framework does not buy anything, but makes things even more complicated. It is also fragile, since packaging the structure in NSData does not take into account things like endian-ness, etc.

The hard part of the batching process using NSCoding is that you really donโ€™t know what the overhead of the coding process is, therefore, to be as large as possible, but still under the maximum packet size is difficult ...

I imagine this without testing or guarantee, but if you want to participate in this approach, it may be so. Be careful, I did not check if my arbitrary 100 bytes for the overhead were realistic. You will have to play a little with numbers.

Packet.h:

  @interface Packet : NSObject <NSCoding> { NSString* fileName; NSInteger fileType; NSUInteger totalPackets; NSUInteger packetIndex; NSData* packetContents; } @property (readonly, copy) NSString* fileName; @property (readonly, assign) NSInteger fileType; @property (readonly, assign) NSUInteger totalPackets; @property (readonly, assign) NSUInteger packetIndex; @property (readonly, retain) NSData* packetContents; + (NSArray*)packetsForFile: (NSString*)name ofType: (NSInteger)type withData: (NSData*)fileContents; @end 

Packet.m:

 #import "Packet.h" @interface Packet () @property (readwrite, assign) NSUInteger totalPackets; @property (readwrite, retain) NSData* packetContents; @end @implementation Packet - (id)initWithFileName: (NSString*)pFileName ofType: (NSInteger)pFileType index: (NSUInteger)pPacketIndex { if (self = [super init]) { fileName = [pFileName copy]; fileType = pFileType; packetIndex = pPacketIndex; totalPackets = NSUIntegerMax; packetContents = [[NSData alloc] init]; } return self; } - (void)dealloc { [fileName release]; [packetContents release]; [super dealloc]; } @synthesize fileName; @synthesize fileType; @synthesize totalPackets; @synthesize packetIndex; @synthesize packetContents; - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject: self.fileName forKey: @"fileName"]; [aCoder encodeInt64: self.fileType forKey:@"fileType"]; [aCoder encodeInt64: self.totalPackets forKey:@"totalPackets"]; [aCoder encodeInt64: self.packetIndex forKey:@"packetIndex"]; [aCoder encodeObject: self.packetContents forKey:@"totalPackets"]; } - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { fileName = [[aDecoder decodeObjectForKey: @"fileName"] copy]; fileType = [aDecoder decodeInt64ForKey:@"fileType"]; totalPackets = [aDecoder decodeInt64ForKey:@"totalPackets"]; packetIndex = [aDecoder decodeInt64ForKey:@"packetIndex"]; packetContents = [[aDecoder decodeObjectForKey:@"totalPackets"] retain]; } return self; } + (NSArray*)packetsForFile: (NSString*)name ofType: (NSInteger)type withData: (NSData*)fileContents { const NSUInteger quanta = 8192; Packet* first = [[[Packet alloc] initWithFileName:name ofType:type index: 0] autorelease]; // Find out how big the NON-packet payload is... NSMutableData* data = [NSMutableData data]; NSKeyedArchiver* coder = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease]; [first encodeWithCoder: coder]; [coder finishEncoding]; const NSUInteger nonPayloadSize = [data length]; NSMutableArray* packets = [NSMutableArray array]; NSUInteger bytesArchived = 0; while (bytesArchived < [fileContents length]) { Packet* nextPacket = [[[Packet alloc] initWithFileName: name ofType: type index: packets.count] autorelease]; NSRange subRange = NSMakeRange(bytesArchived, MIN(quanta - nonPayloadSize - 100, fileContents.length - bytesArchived)); NSData* payload = [fileContents subdataWithRange: subRange]; nextPacket.packetContents = payload; bytesArchived += [payload length]; [packets addObject: nextPacket]; } for (Packet* packet in packets) { packet.totalPackets = packets.count; } return packets; } - (NSData*)dataForSending { NSMutableData* data = [NSMutableData data]; NSKeyedArchiver* coder = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease]; [self encodeWithCoder: coder]; [coder finishEncoding]; return [NSData dataWithData:data]; } + (Packet*)packetObjectFromRxdData:(NSData*)data { NSKeyedUnarchiver* decoder = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease]; return [[[Packet alloc] initWithCoder:decoder] autorelease]; } @end 

Reassembling the source file from these packages can be done using the same approach as splitting it ... Iterating over packages, copying from a separate NSDatas package load to a large NSMutableData.

In conclusion, I feel compelled to say that when you do something like this, it comes down to introducing a primitive TCP stack, usually you have to stop there and ask if there are any better ways to do this. In other words, if GameKit was the best way to transfer files between devices via Bluetooth, you would expect the API to have a method for this, but instead it has this 8K limit.

I'm not intentionally cryptic - I donโ€™t know what the correct API will be for your situation, but the exercise of preparing this Packet class left me in my head: "There must be a better way."

Hope this helps.

+8
source

You create an NSData with sizeof (package), which is just the size of the pointer. Change it to sizeof (file_packet).

By the way, you really do not send the file name and its contents. Only pointers to them.

+4
source

All Articles