How to: Implement server forcing disconnect of client in GKSession - objective-c

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.

Related

Bidirectional communication in Distributed Objects in cocoa

I am new to Cocoa programming...I am learning IPC with Distributed Objects.
I have made a simple example where I am vending objects from the server and calling them in the client.I am successful in passing messages from client object to the server But I want to pass messages from server to client[Bidirectional]...How do I do that?
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
MYMessageServer *server = [[MYMessageServer alloc] init];
NSConnection *defaultConnection=[NSConnection defaultConnection];
[defaultConnection setRootObject:server];
if ([defaultConnection registerName:#"server"] == NO)
{
NSLog(#"Error registering server");
}
else
NSLog(#"Connected");
[[NSRunLoop currentRunLoop] configureAsServer];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
//Getting an Vended Object
server = [NSConnection rootProxyForConnectionWithRegisteredName:#"server" host:nil];
if(nil == server)
{
NSLog(#"Error: Failed to connect to server.");
}
else
{
//setProtocolForProxy is a method of NSDistantObject
[server setProtocolForProxy:#protocol(MYMessageServerProtocol)];
[server addMessageClient:self];
[server broadcastMessageString:[NSString stringWithFormat:#"Connected: %# %d\n",
[[NSProcessInfo processInfo] processName],
[[NSProcessInfo processInfo] processIdentifier]]];
}
}
- (void)appendMessageString:(NSString *)aString
{
NSRange appendRange = NSMakeRange([[_messageView string] length], 0);
// Append text and scroll if neccessary
[_messageView replaceCharactersInRange:appendRange withString:aString];
[_messageView scrollRangeToVisible:appendRange];
}
- (void)addMessageClient:(id)aClient
{
if(nil == _myListOfClients)
{
_myListOfClients = [[NSMutableArray alloc] init];
}
[_myListOfClients addObject:aClient];
NSLog(#"Added client");
}
- (BOOL)removeMessageClient:(id)aClient
{
[_myListOfClients removeObject:aClient];
NSLog(#"Removed client");
return YES;
}
- (void)broadcastMessageString:(NSString *)aString
{
NSLog(#"Msg is %#",aString);
self.logStatement = aString;
[_myListOfClients makeObjectsPerformSelector:#selector(appendMessageString:)
withObject:aString];
}
#protocol MYMessageServerProtocol
- (void)addMessageClient:(id)aClient;
- (BOOL)removeMessageClient:(id)aClient;
- (void)broadcastMessageString:(NSString *)aString;
You haven't shown the code for MYMessageServer or MYMessageServerProtocol, specifically the -addMessageClient: method. However, once you have passed the server a reference to an object in the client, the server can message that object like normal and that message will be sent over D.O. to the client.
So, the client is sending self (its application delegate) to the server via -addMessageClient:. The server can simply call methods on the object it receives in its implementation of -addMessageClient: and that will call methods on the client's application delegate object. The server can keep that reference somewhere, such as an array of clients in an instance variable, and continue to message the client at later times, too, if it wants. The server will want to clear that reference out when the connection closes, which it can detect from notifications that NSConnection posts.

Design pattern for handling a server response

I've an observer pattern on the UI that checks what's the status of an object that handles a server connection that's trying to update a certain field on a database.
The UI's update method receives an object containing data pairs containing the information of what's happening with the connection. The problem is that I'm getting tangled with a lot of ifs checking for different possibilities.
- (void) update:(Bundle *)arg
{
if ([[arg getData:#"updatee"] isEqualToString:#"email"]){
UITableViewCell *emailCell = [[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
if ([[arg getData:#"connecting"] isEqualToString:#"true"]) {
//Email is being posted
[_emailLabel_email setText:#"Connecting..."];
[_emailLabel_set setHidden:YES];
emailCell.accessoryType = UITableViewCellAccessoryNone;
[_emailActivityIndicator startAnimating];
}else{
if ([[arg getData:#"succesfull"] isEqualToString: #"false"])
//Email was posted unsuccesfully
[[[UIAlertView alloc] initWithTitle:#"Taken Email Address"
message:#"The email address that you entered is already in use, please double check it"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] show];
else{
//Email was posted succesfully.
[_emailLabel_set setText:#"Change"];
}
[_emailActivityIndicator stopAnimating];
[_emailLabel_email setText:[mng getEmail]];
[_emailLabel_set setHidden:NO];
emailCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
}
//Password cases
}
}
As the server responds with a string I'm finding difficult to avoid this spaggetti of code.
Which would be the smarter object to send on the update method?
You could keep the data how it is and put all of the values you expect for [arg getData:#"updatee"] as the keys in a dictionary. The values would be the string representation of method selectors, where each of the methods handles one of the cases in your current if statement. Now your update: method just gets the selector string from the dictionary, converts it to a real selector (NSSelectorFromString) and calls the method (passing the arg as a parameter, all methods used here must have matching parameter listing). If you receive an unexpected update type nothing would happen so you should log / assert that. If you get an exception while translating the selector you probably have a typo (good candidate for a test which executes all of the methods in the configuration).
Of course you could basically do the same thing with all the methods and a dirty big if / else statement.

How to separate data packets when using GCDASyncSocket

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.

Change the page after an action is done

I have 2 pages with xib files. One is a login page and the other is a welcome page.
Here is the login function:
-(IBAction)login{
NSLog(#"login begin");
NSString *loginResponse = [self tryLogin];
NSLog(#"%#", loginResponse);
loginError.text = loginResponse;
NSUserDefaults *session = [NSUserDefaults standardUserDefaults];
[session setInteger:1 forKey:#"islogged"];
}
After the user is logged in successfully I need to redirect them to another xib file which is called Notifications. Now how exactly should I do this?
Also for some reason when I put an if statement inside the function, it doesnt go into the statements.
-(IBAction)login{
NSLog(#"login begin");
NSString *loginResponse = [self tryLogin];
NSLog(#"%#", loginResponse);
if(responseInt == #"2"){
NSLog(#"login error 2");
UIAlertView *alertURL = [[UIAlertView alloc] initWithTitle:#"Error!"
message:#"Username or password is wrong!"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alertURL show];
[alertURL release];
///////////////////////////////////////////////////
}else if(responseInt == #"0"){
NSUserDefaults *session = [NSUserDefaults standardUserDefaults];
[session setInteger:1 forKey:#"islogged"];
}
}
As you can see I try to nslog something as soon as one of the conditions are met but it does not go into any of the statements (the loginResponse is sent back as 2).
How to change views
If you app is navigation-based then you can add new fullscreen views using method [self.navigationController pushViewController:controller animated:YES];.
Everywhere you can add new fullscreen view using next method : [self presentModalViewController:controller animated:YES];.
Issue with 'if-statement'.
When you want to compare to objects you should use one of following methods:
compare:, this method return one of following values: NSOrderedSame, NSOrderedDescending, NSOrderedAscending. For example, if ([responseInt compare:#"2"] == NSOrderedSame) {// equal}
'isEqual:', for example, if ([responseInt isEqual:#"2"]) {// equal}
or some specific comparators for concrete class
Don't forget that when you are comparing using == or > and etc you are comparing addresses of objects in memory.
Have you tried doing isEqualToString: instead of the equal sign? That is a more reliable string comparator. Also it seems strange that your login method is returning a string and not an integer. Is there a reason for that?

Populating NSImage with data from an asynchronous NSURLConnection

I have hit the proverbial wall trying to figure out how to populate an NSImage with data returned from an asynchronous NSURLConnection in my desktop app (NOT an iPhone application!!).
Here is the situation.
I have a table that is using custom cells. In each custom cell is an NSImage which is being pulled from a web server. In order to populate the image I can do a synchronous request easily:
myThumbnail = [[NSImage alloc] initWithContentsOfFile:myFilePath];
The problem with this is that the table blocks until the images are populated (obviously because it's a synchronous request). On a big table this makes scrolling unbearable, but even just populating the images on the first run can be tedious if they are of any significant size.
So I create an asynchronous request class that will retrieve the data in its own thread as per Apple's documentation. No problem there. I can see the data being pulled and populated (via my log files).
The problem I have is once I have the data, I need a callback into my calling class (the custom table view).
I was under the impression that I could do something like this, but it doesn't work because (I'm assuming) that what my calling class really needs is a delegate:
NSImage * myIMage;
myImage = [myConnectionClass getMyImageMethod];
In my connection class delegate I can see I get the data, I just don't see how to pass it back to the calling class. My connectionDidFinishLoading method is straight from the Apple docs:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// do something with the data
// receivedData is declared as a method instance elsewhere
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
// release the connection, and the data object
[connection release];
[receivedData release];
}
I am hoping this is a simple problem to solve, but I fear I am at the limit of my knowledge on this one and despite some serious Google searches and trying many different recommended approaches I am struggling to come up with a solution.
Eventually I will have a sophisticated caching mechanism for my app in which the table view checks the local machine for the images before going out and getting them form the server and maybe has a progress indicator until the images are retrieved. Right now even local image population can be sluggish if the image's are large enough using a synchronous process.
Any and all help would be very much appreciated.
Solution Update
In case anyone else needs a similar solution thanks to Ben's help here is what I came up with (generically modified for posting of course). Bear in mind that I have also implemented a custom caching of images and have made my image loading class generic enough to be used by various places in my app for calling images.
In my calling method, which in my case was a custom cell within a table...
ImageLoaderClass * myLoader = [[[ImageLoaderClass alloc] init] autorelease];
[myLoader fetchImageWithURL:#"/my/thumbnail/path/with/filename.png"
forMethod:#"myUniqueRef"
withId:1234
saveToCache:YES
cachePath:#"/path/to/my/custom/cache"];
This creates an instance of myLoader class and passes it 4 parameters. The URL of the image I want to get, a unique reference that I use to determine which class made the call when setting up the notification observers, the ID of the image, whether I want to save the image to cache or not and the path to the cache.
My ImageLoaderClass defines the method called above where I set what is passed from the calling cell:
-(void)fetchImageWithURL:(NSString *)imageURL
forMethod:(NSString *)methodPassed
withId:(int)imageIdPassed
saveToCache:(BOOL)shouldISaveThis
cachePath:(NSString *)cachePathToUse
{
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:imageURL]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData that will hold
// the received data
// receivedData is declared as a method instance elsewhere
receivedData = [[NSMutableData data] retain];
// Now set the variables from the calling class
[self setCallingMethod:methodPassed];
[self setImageId:imageIdPassed];
[self setSaveImage:shouldISaveThis];
[self setImageCachePath:cachePathToUse];
} else {
// Do something to tell the user the image could not be downloaded
}
}
In the connectionDidFinishLoading method I saved the file to cache if needed and made a notification call to any listening observers:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
// Create an image representation to use if not saving to cache
// And create a dictionary to send with the notification
NSImage * mImage = [[NSImage alloc ] initWithData:receivedData];
NSMutableDictionary * mDict = [[NSMutableDictionary alloc] init];
// Add the ID into the dictionary so we can reference it if needed
[mDict setObject:[NSNumber numberWithInteger:imageId] forKey:#"imageId"];
if (saveImage)
{
// We just need to add the image to the dictionary and return it
// because we aren't saving it to the custom cache
// Put the mutable data into NSData so we can write it out
NSData * dataToSave = [[NSData alloc] initWithData:receivedData];
if (![dataToSave writeToFile:imageCachePath atomically:NO])
NSLog(#"An error occured writing out the file");
}
else
{
// Save the image to the custom cache
[mDict setObject:mImage forKey:#"image"];
}
// Now send the notification with the dictionary
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:callingMethod object:self userInfo:mDict];
// And do some memory management cleanup
[mImage release];
[mDict release];
[connection release];
[receivedData release];
}
Finally in the table controller set up an observer to listen for the notification and send it off to the method to handle re-displaying the custom cell:
-(id)init
{
[super init];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(updateCellData:) name:#"myUniqueRef" object:nil];
return self;
}
Problem solved!
My solution is to use Grand Central Dispatch (GCD) for this purpose, you could save the image to disc too in the line after you got it from the server.
- (NSView *)tableView:(NSTableView *)_tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
SomeItem *item = [self.items objectAtIndex:row];
NSTableCellView *cell = [_tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
if (item.artworkUrl)
{
cell.imageView.image = nil;
dispatch_async(dispatch_queue_create("getAsynchronIconsGDQueue", NULL),
^{
NSURL *url = [NSURL URLWithString:item.artworkUrl];
NSImage *image = [[NSImage alloc] initWithContentsOfURL:url];
cell.imageView.image = image;
});
}
else
{
cell.imageView.image = nil;
}
return cell;
}
(I am using Automatic Reference Counting (ARC) therefore there are no retain and release.)
Your intuition is correct; you want to have a callback from the object which is the NSURLConnection’s delegate to the controller which manages the table view, which would update your data source and then call -setNeedsDisplayInRect: with the rect of the row to which the image corresponds.
Have you tried using the initWithContentsOfURL: method?