How to separate data packets when using GCDASyncSocket - objective-c

Can anyone help me? I intensively exchange data between two devices over TCP protocol by using GCDAsyncSocket. I send data like this:
NSMutableDictionary *packet = [[[NSMutableDictionary alloc] init] autorelease];
[packet setObject:[NSNumber numberWithInt:MultiPlayerTypeInfoNextRoundConfirm] forKey:#"type_info"];
[packet setObject:[NSNumber numberWithBool:YES] forKey:#"connection_confirmation"];
NSMutableData *data = [[NSMutableData alloc] initWithData:[NSKeyedArchiver archivedDataWithRootObject:packet]]; //[NSKeyedArchiver archivedDataWithRootObject:packet];
if (currentGameMode == GameModeServer)
[(ServerMultiplayerManager *)multiplayerManager sendNetworkPacket:data withTag:MultiPlayerTypeInfoNextRoundConfirm];
- (void)sendNetworkPacket:(NSData *)data withTag:(long)tag
{
[asyncSocket writeData:data withTimeout:-1 tag:tag];
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
NSLog(#"DID WRITE DATA tag is %ld", tag);
[sock readDataWithTimeout:-1 tag:0];
}
I read data like this:
- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
NSString *receivedInfo = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
[info_data setData:data];
NSLog(#"DID READ DATA WITH TAG %ld", tag);
if ([receivedInfo isEqualToString:ROOM_FILLED])
{
isMaster = (tcpRequest.identifier == MASTER_CHAR);
NSLog(#"IS MASTER SET %d", isMaster);
[multiplayerDelegate setGameModeServer];
[multiplayerDelegate startGame];
}
else
[self dataProcessing:info_data];
[sender readDataWithTimeout:-1 tag:0];
}
- (void)dataProcessing:(NSData *)data
{
NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithData:data];
MultiPlayerTypeInfo typeInfo = [[dict objectForKey:#"type_info"] intValue];
}
My issue that these packets of data get messed. Say a packet marked with tag 10 is read at the receiver device as packet marked with tag 11, which was sent immediately after packet 10, and when it comes to unarchiving of actual packet 11 NSKeyedUnarchiver throws exception Incomprehensible archive.
As far as i understand i should separate the packets somehow. What i tried was appending separatory symbols to the data being sent:
[data appendData:[#"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
and trying to read it like this:
[socket readDataToData:[GCDAsyncSocket CRLFData] timeout:-1 tag:some_tag];
But it didn't help. What am i doing wrong and what should i do instead?

I guess, you misunderstood the role of the tag. GCDAsyncSocketis (as the name suggests) an asynchrone socket. The tag helps you to match the received data with the receive order and the send succes with the send order, resp.
E.g., if you want to send data, you use writeData:messageA withTimeout:-1 tag: tagA (or something similar) to give your socket the order to send somewhen in the near future. It won't be necessarily right now. And you can immediately give the next order to send another message, say messageB with tag tagB.
To know, that the messageA was really sent, you get the notification via socket:aSocket didWriteDataWithTag:aTag. Here, aTaghas the value of tagA if messageAwas sent, and the value of tagB if messageB was sent. The tag is not sent with the message; it only helps you to identify your order.
It is the very same thing at the receiving side. You give the order to receive (somewhen) some data and assign a tag to that very order. Once you did receive data, the notification (via socket:didReadData:withTag:) shows you the tag to let you know, which order succeed.
You may use the tag for some semantic information and put it in your message. But even then, the tag in the notification is the tag of the receive order, but never the one of the send order. If you want to use the tag you put in the message at the receiving side, you have to receive (at least parts of) the message first and parse it.
To come to the core of your issue: You have basically two possibilities to know, which kind of data is arriving:
Know the sequence of sent data and receive it in the very same order.
Use a message head that identifies the kind of data. Receive only the head and receive and parse the remains of your message in dependence of the head data.
EDIT
Here is an example for the 2nd approach. Assume you can sent a number of object of classes A, B, etc. Your header could include type and size of your data:
typedef struct {
NSUInteger type_id;
NSUInteger size;
} header_t;
#define typeIdA 1
#define typeIdB 2
// ...
Once you want to send an object obj with objKey:
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:obj forKey: objKey];
header_t head;
if ([obj class] == [A class]) {
head.type_id = typeIdA;
} else if ([obj class] == [B class]) {
head.type_id = typeIdB;
} else ...
// ....
header.size = data.lengh;
NSData* headData = [NSData dataWithBytes: &header length: sizeof(header)];
dataWithBytes:length:
header = NSData.length;
[asyncSocket writeData:headData withTimeout:-1 tag:headTag];
[asyncSocket writeData:data withTimeout:-1 tag:dataTag];
If you want, you can get notifications on successful sending or errors, but I skip this here.
At receiver side, you expect a header first:
[receiveSocket readDataToLength:sizeof(header_t) withTimeout:-1 tag:rcvHdrTag];
// rcvHdrTag must not match one of the typeIdX tags
In your socket:didReadData:withTag: you have to distinguish, if you get the header or the remains (the receiving of the remains is initiated here!)
- (void)socket:(GCDAsyncSocket *)aSocket didReadData:(NSData *)data withTag:(long)tag {
header_t head;
id obj;
id key;
switch (tag) {
case rcvHdrTag:
[data getBytes:&head length:sizeof(header)];
// now you know what to receive
[aSocket readDataToLength:header.size withTimeout:-1 tag:header.type];
return;
break; // I know, redundancy :-)
case typeIdA:
objKey = objKeyA; // whatever it is...
break;
case typeIdB:
objKey = objKeyB;
// ....
}
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
obj = [unarchiver decodeObjectForKey:objKey];
// store your object ...
}
This is not the most elegant example, and it ignores object trees and inter-object dependencies in archives, but you should get the idea.

Related

CoreBluetooth: unable to write and retrieve a static characteristic

I am building an application where two iOS devices both transmit and scan (peripheral and central) for each other. Due to Apple's implementation, when the app is backgrounded, all identifiable information is removed from the advertising packet, meaning I need to connect to the discovered peripherals to find out who and what they are if they are transmitting in the background.
All I really need to do is identify the peripheral. (Connect and disconnect). Currently, the only way I can find to do this is to set a static characteristic attached to a common service that allows each device to uniquely identify itself, even when backgrounded. This value will not change or get updated. If I could simply look at peripheral.UUID after connecting, this would do the trick. But I can't anymore with iOS8. So, I create a characteristic to contain the unique identifier.
(Not sure if this is the best way, but its the only way I can think of.)
Everything is working great (discovering characteristic) but I am unable to retrieve anything other than nil for the characteristic, even though I have specifically set it when I started transmitting.
Here is my (Peripheral code):
-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
// Opt out from any other state
if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
return;
}
NSLog(#"BT Transmitter Powered On");
NSString* uniqueString = #“foobar";
NSData* characteristicValue = [uniqueString dataUsingEncoding:NSUTF8StringEncoding];
self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:#"08590F7E-DB05-467E-8757-72F6FAEB13D4"]
properties:CBCharacteristicPropertyRead
value:characteristicValue
permissions:CBAttributePermissionsReadable];
CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:#"E20A39F4-73F5-4BC4-A12F-17D1AD07A961"] primary:YES];
transferService.characteristics = #[self.transferCharacteristic];
[self.peripheralManager addService:transferService];
[self.peripheralManager startAdvertising:#{ CBAdvertisementDataServiceUUIDsKey: #[[CBUUID UUIDWithString:#"E20A39F4-73F5-4BC4-A12F-17D1AD07A961"]] }];
}
And here is my Central Code:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics) {
// print out value of discovered characteristic
NSLog (#"Characteristic discovered: %#", characteristic); // this outputs all all the properties of the characteristic, including a value of "null".
NSString *value = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"Value: %#",value); // this prints out nothing
}
}
What am I doing wrong? I would expect to see the value of the characteristic as "foobar" when transformed back into an NSString. Instead it is null.
Having discovered the characteristic you need to perform a read request to actually get its value -
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics) {
// print out value of discovered characteristic
NSLog (#"Characteristic discovered: %#", characteristic); // this outputs all all the properties of the characteristic, including a value of "null".
if ([characteristic.UUID.UUIDString isEqualToString:#"08590F7E-DB05-467E-8757-72F6FAEB13D4"]) {
[peripheral readValueForCharacteristic:characteristic];
NSString *value = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"Value: %#",value); // this prints out nothing
}
}
You will subsequently get a call to didUpdateValueForCharacteristic: -
-(void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error == nil) {
NSString *valueString=[[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"The value=%#",valueString
}
}
Update: to find the RSSI of the peripheral once you have connected to it and read its service, use the readRSSI method.
Then, strangely enough, even though its not in the documentation, this is the only delegate callback method (with RSSI) that works for me running 8.1.1.
-(void) peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error {
NSLog(#"Got RSSI update in didReadRSSI : %4.1f", [RSSI doubleValue]);
}
Now I just have to figure out how to link this RSSI signal with the specific peripheral I connected to and identified in the previous call.

Pass any data type between devices with multipeer connectivity

I am trying to implement the multipeer connectivity framework into my application.
I have successfully done this. What I want the user to be able to do is select something like a picture from the camera roll and pass it over to another connected device. I'm doing it with other things though, not just UIImage, (e.g. NSString, NSObject...)
Ideally, what I want to be able to do is to be able to use it and receive it using one of the two methods:
- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID;
OR
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
What I want, however, is a standardised way (for any object type) to pass it over to another device using multipeer connectivity.
My only thought was to convert each object into NSData and then pass it over, however, this does not work on the receiving end. My test is:
NSData *myData = [NSKeyedArchiver archivedDataWithRootObject:self.myImage];
NSLog(#"%#", myData);
Then I have no idea how to convert it back. Is it something to do with NSCoding?? Any ideas would be much appreciated! :) Thank you!!
Sounds like you have the right idea, you just need to use NSKeyedUnarchiver when the data is received.
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
id myObject = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
From there you can determine what kind of object you actually received:
if ([myObject isKindOfClass:[SomeClass class]]){
//Handle
}
This will work for any class, as long as it conforms to the NSCoding protocol. Take a look at: Encoding and Decoding Objects
What I would suggest is implementing a protocol to transfer NSData objects between devices. Have a standardised packet that you send between devices. Such as
type | length | data....
The type and length should be integers so when they get to the other side you know exactly how big they are. The length will then tell you how long your actual packet is.
A simple example
// method received "(id) data" which can be UIImage, NSString, NSDictionary, NSArray
// 1 -> Image
// 2 -> JSON
uint32_t type;
if ([data isKindOfClass:[UIImage class]]) {
data = UIImageJPEGRepresentation((UIImage *)data, 1.0);
type = 0;
} else {
data = [data JSONData];
type = 1;
}
uint32_t length = [data length];
NSMutableData *packet = [NSMutableData dataWithCapacity:length + (INT_32_LENGTH * 2)];
[packet appendBytes:&type length:INT_32_LENGTH];
[packet appendBytes:&length length:INT_32_LENGTH];
[packet appendData:data];
Then on the other end you just read the length of the packet check the type and convert back to the correct object type. For Images send as binary packet and for anything else send as JSON.
Hope that helps.

NSURLConnection lost data

I need to communicate with server with SOAP request and get response. I have quite good method which creates XML request with parameters, transform it to NSMutableURLRequest and send with NSURLConnection. All of it works fine so I'll skip this part of code. My server is kind of Magento shop so I receive different amount of data depends what request I use. When I get short responses like session ID, everything works perfect, but when response is longer (list of countries for example) my data is lost somehow. I checked it by comparison of data length in didReceiveResponse and didReceiveData
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.expectedLength = response.expectedContentLength;
self.downloadedLength = 0;
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"Data recived");
self.downloadedLength = self.downloadedLength + [data length];
[receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (self.downloadedLength == self.expectedLength) {
NSLog(#"correctly downloaded");
}
else{
NSLog(#"sizes don't match");
}
str = [[NSString alloc] initWithBytes:[receivedData bytes] length:[receivedData length] encoding:NSISOLatin1StringEncoding];
NSData *tmp_Data = [[NSData alloc] initWithData:[str dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES]];
parser = [[SYXmlParser alloc]initWithData:tmp_Data];
[parser startParser];
if([parser theDataArray] != nil && [[parser theDataArray]count] != 0)
{
resultArray = [[NSMutableArray alloc]initWithArray:[parser theDataArray]];
[self performSelectorOnMainThread:#selector(loadFinished) withObject:nil waitUntilDone:YES];
}
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
When response is long I got cut XML and my comparison returned "sizes don't match". XML is not cut definitely by half, but the data is missing in the middle of text, like some of characters is missing only. The issue repeats every time in same places in same way so its not random. I tried use AFNetworking to solve this problem, but I think I cant use it properly with this SOAP request. I will be grateful for every proposition which would fix this problem.
EDIT:
I used this Charles but there is the same problem as in xcode. When I open response tab and SOAP tab the response is null, on XML tab there is an error that Charles could't parse this received data, but in overview tab there is size of response 4666 bytes. Is that mean that server gives bad response? I cant believe it because its commercial server Magento which is used with many others languages and it works.

Objective-C: Unable to send/receive data after two matchmaking two devices using Game Center

I was hoping someone would be able to help with an Objective-C problem I have relating to sending and receiving information between two devices using GameCenter once they have been through matchmaking.
I am using a textbook called Beginning iOS Game Center and Game Kit as my guide, and it is going through an example program, but I am stuck on the part where I would like to receive data from a device playing the game.
I can successfully matchmake two devices and the appropriate view to appear. I have two functions in my GameCenterManager.m file to send information - one being the following:
- (void)sendStringToAllPeers:(NSString *)dataString reliable:(BOOL)reliable
{
NSLog(#"Send String To All Peers");
NSLog(#"Data String: %#", dataString);
NSLog(#"match or session %#", self.matchOrSession);
if (self.matchOrSession == nil)
{
NSLog(#"GC Manager matchorsession ivar was not set - this needs to be set with the GKMatch or GKSession before sending or receiving data");
return;
}
NSData *dataToSend = [dataString dataUsingEncoding:NSUTF8StringEncoding];
GKSendDataMode mode;
if (reliable)
{
mode = GKSendDataReliable;
}
else{
mode = GKSendDataUnreliable;
}
NSError *error = nil;
if ([self.matchOrSession isKindOfClass:[GKSession class]])
{
NSLog(#"Match or session 1");
NSLog(#"Data to send: %#", dataToSend);
[self.matchOrSession sendDataToAllPeers:dataToSend withDataMode:mode error:&error];
}
else if ([self.matchOrSession isKindOfClass:[GKMatch class]])
{
NSLog(#"Match or session 2");
NSLog(#"Data to send: %#", dataToSend);
[self.matchOrSession sendDataToAllPlayers:dataToSend withDataMode:mode error:&error];
}
else
{
NSLog(#"GC Manager matchOrSession was not a GKMatch or a GK Session, we are unable to send data");
}
if (error != nil)
{
NSLog(#"An error occurred while sending data %#", [error localizedDescription]);
}
}
this function I call from a function in my racetohundredViewController.m file:
- (void)generateAndSendHostNumber;
{
NSLog(#"Generate and send host number");
randomHostNumber = arc4random();
NSString *randomNumberString = [NSString stringWithFormat: #"$Host:%f", randomHostNumber];
NSLog(#"the random number string is: %#", randomNumberString);
[self.gcManager sendStringToAllPeers:randomNumberString reliable: YES];
}
I successfully get the following NSLog output resulting from this:
2013-01-02 22:27:43.519 First to 50[1376:907] Send String To All Peers
2013-01-02 22:27:43.520 First to 50[1376:907] Data String: $Host:2087825492.000000
2013-01-02 22:27:43.521 First to 50[1376:907] match or session <GKMatch 0x200853d0 expected count: 0 seqnum: 2
G:1656671636:connected
reinvitedPlayers:(
)>
2013-01-02 22:27:43.522 First to 50[1376:907] Match or session 2
2013-01-02 22:27:43.523 First to 50[1376:907] Data to send: <24486f73 743a3230 38373832 35343932 2e303030 303030>
So I can see the 'Data to send' output which is great.
However I now have the command
[self.matchOrSession sendDataToAllPeers:dataToSend withDataMode:mode error:&error];
which doesn't seem to take me anywhere at all. I have the following function in GameCenterManager.m:
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context
{
NSLog(#"*****Receive Data In Session");
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *dataDictionary = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:dataString, peer, session, nil] forKeys:[NSArray arrayWithObjects: #"data", #"peer", #"session", nil]];
[self callDelegateOnMainThread: #selector(receivedData:) withArg: dataDictionary error: nil];
}
But I am not seeing the NSLog output from this. Likewise, I have a function in my racetohundredViewController.m file
- (void)receivedData:(NSDictionary *)dataDictionary
{
NSLog(#"------Received Data");
}
which also doesn't get called; presumably because the previous function isn't able to call it.
I have been trying to work out why this doesn't work for a while now without any avail. Can anyone point out where I am going wrong? I hope I have put in all the relevant code but if you have any questions please ask.
Thanks to all, in advance.
I realised what I had done after looking online at other people's problems with the same thing. I had not set my delegate after finding a match, a very rookie mistake!

How to: Implement server forcing disconnect of client in GKSession

I am implementing the GKSession server/client mode operation in my application on iOS. I found one question related to mine but with no answer. I am trying to allow the server to disconnect any client that is currently connected to the session. I thought that calling disconnectPeerFromAllPeers:(NSString *)peerID would allow me to do this, but is seems to have no effect.
Any suggestions?
Thanks
Actually answered via question update ion 01/03/2012, but moved this text to the answer section
I wanted to share how I implemented a disconnect request sent from server to client. All of the code presented below is contained within a class I created to completely encapsulate all the interfacing with a GKSession instance (also implements the GKSessionDelegate methods).
First I have the server send a disconnect request to the client that shall be disconneted. Any data that is sent from client to server or vice versa is contained within a dictionary that also has a key-value pair to specify the type of data that is sent (in this case the data is a disconnect request).
- (void)sendDisconnectRequestToPeer:(NSString *)peer {
//create the data dictionary that includes the disconnect value for the data type key
NSMutableDictionary *dictPrvw = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:GKSessionDataTypeDisconnect], kDictKeyDataType, nil];
NSData *dataChunk = [[NSKeyedArchiver archivedDataWithRootObject:dictPrvw] retain];
//[self printDict:dictPrvw];
NSArray *peers = [[NSArray alloc] initWithObjects:peer, nil];
[self sendData:dataChunk toPeers:peers];
[dataChunk release];
[dictPrvw release];
}
The client receives the data, casts it into a dictionary and examines the key-value pair that specifies what type of data was sent. If it's a disconnect request, my "GKSessionManager" class then implements a disconnect.
- (void)recievedAllDataChunksInSession:(GKSession *)session fromPeer:(NSString *)peer context:(void *)context {
//The chunk was packaged by the other user using an NSKeyedArchiver,
//so unpackage it here with our NSKeyedUnArchiver
NSMutableDictionary *responseDictionary = (NSMutableDictionary *)[[NSKeyedUnarchiver unarchiveObjectWithData:self.recievedPackets] mutableCopyWithZone:NULL];
//[self printDict:responseDictionary];
//get the enumerator value for the data type
NSNumber *gkSessDataType = [responseDictionary objectForKey:kDictKeyDataType];
int intDataType = [gkSessDataType intValue];
UIAlertView *anAlrtVw;
switch (intDataType) {
case GKSessionDataTypeMessageData:
[self sessionManager:self recievedDataDictionary:responseDictionary];
break;
case GKSessionDataTypePreviewRequest:
if (sess.sessionMode == GKSessionModeServer) {
[self sendMsgPreviewToPeer:peer];
}
break;
case GKSessionDataTypePreviewSend:
//[self sessionManager:self recievedDataDictionary:responseDictionary];
[self sessionManager:self connectedWithPrelimData:responseDictionary];
break;
case GKSessionDataTypeDisconnect:
anAlrtVw = [[UIAlertView alloc]
initWithTitle:nil
message:#"The server has disconnect you."
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[anAlrtVw show];
[anAlrtVw release];
[self closeSession];
[self disconnectedByServer];
default:
break;
}
}
- (void)closeSession {
[sess disconnectFromAllPeers];
[sess setDataReceiveHandler: nil withContext: NULL];
sess.available = NO;
sess.delegate = nil;
self.sess = nil;
self.serverId = nil;
self.rqstPeerId = nil;
serverIsConnecting = NO;
}
The user never sees the disconnect request and so has no control over whether or not to deny it.
Hope this information helps. I realize what I wrote my not be entirely clear and I have left a lot of other code out (on purpose) so feel free to comment or ask questions.