I'm a fairly novice obj-c developer and have a question on how to set up a client-server relationship. I'm designing (mostly as a hobby) a board game to be played over the internet with friends and family (think monopoly). My problem: how do I set up the appropriate client-server relationship to have one server with multiple clients?
My thinking was to have one server contain all the information on the state of the game as well as send appropriate messages to a variety of objects through Cocoa's excellent distributed objects framework. However, I can't figure out how to have one server accept multiple clients.
firstConnection = [NSConnection defaultConnection];
[firstConnection setRootObject: firstPlayer];
[[NSRunLoop currentRunLoop] run];
But then what? Is there a way to tell the run loop to stop when a client is attached? I'd like to avoid multiple threading if possible as that would be a whole new complication to learn and this project is already challenging enough!
Any help would be greatly appreciated and I'd be happy to clarify anything at all if necessary.
Thanks in advance.
Basically the strategy to take is to have the server register itself as the root object. When the client connects to the server, it sends the server a connection message (defined by the server's protocol you create) that allows the server to register that client in order to send messages to it in the future. This could be as simple as adding the client to an array; no special run loops or threads should be needed.
Here's a quick example to communicate across processes, from a test app I wrote back when I was learning DO for the first time. Once the setup is done you can add code to make the server send messages to one or more objects in the _clients array based on any event you'd like, including setting up a timer for a rough game loop.
Server:
- (void)registerClient:(byref Client *)client;
{
[_clients addObject:client];
}
- (void)awakeFromNib;
{
_clients = [[NSMutableArray alloc] init];
[[NSConnection defaultConnection] setRootObject:self];
if ( [[NSConnection defaultConnection] registerName:#"server"] == NO )
{
// error code!
}
}
Client:
- (void)awakeFromNib;
{
id theProxy;
theProxy = [[NSConnection rootProxyForConnectionWithRegisteredName:#"server" host:nil] retain];
[theProxy setProtocolForProxy:#protocol(ServerP)];
if ( theProxy == nil )
// error code!
[theProxy registerClient:self];
}
Keep in mind that there are a lot of "gotchas" in distributed objects! Start simple, even if it means developing a rough prototype of your game idea first.
Cocoa's excellent distributed objects framework
That's the first time I've seen those words together like that ;)
Related
My Goal is to develop robust, coherent, and persistent DB that can be shared between processes, just list Windows Registry.
On a previous question I advised not to use CFPreferences (and NSUserDefaults) due to the following reason
Current versions of macOS have a great deal of difficulty, and sometimes outright refuse, to update the values in one process with the values set by a second process.
Alternatively, I advised to use the following approach:
To have one process responsible for all of the shared values and the other processes get/set those values via XPC.
The XPC is quite new to me but from what I've read so far, it seems to use GCD queues for each connection, so how can I keep coherency if there are multiple processes asking to access the same DB for R/W operations (how can I enforce single thread to execute items in all queues).
Furthermore, I'd like to make this DB to fulfill ACID requirements, how can I achieve this ?
Here's my suggestion, and the solution I use in my applications.
(1) Create a named XPC service.
If you need to connect with your service from multiple apps, you'll need to name and register your service app with launchd.
(XPC make it pretty easy to create an anonymous service used only by your app, but connecting from other apps gets a little trickier. Start with the Daemons and Services Programming Guide.)
Note that in my solution, I already had a user agent registered with launchd, so this was just a matter of moving on to step (2).
(2) Add XPC message handlers to get and set the values you want to share.
- (void)queryPreferenceForKey:(NSString*)key withReply:(void(^)(id value))reply
{
reply([[NSUserDefaults standardUserDefaults] objectForKey:key]);
}
- (void)setPreferenceValue:(id)value forKey:(NSString*)key withReply:(void(^)(BOOL changed))reply
{
BOOL changed = NO;
id previous = [[DDUserPreferences standardUserDefaults] objectForKey:key];
if (!OBJECTS_EQUAL(previous,value))
{
[[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
changed = YES;
}
reply(changed);
}
(3) There is no step 3.
Basically, that's it. The NSUserDefault class is thread safe, so there are no concurrently issues, and it automatically takes care of serializing the property values and synchronizing them with the app's persistent defaults .plist file.
Note: since this is based on NSUserDefaults, the value objects must be property-list objects (NSString, NSNumber, NSArray, NSDictionary, NSDate, NSData, ...). See Preferences and Settings Programming Guide.
I'm trying to figure out how to make certain callbacks trigger.
On the peripheral peripheralManager:central:didSubscribeToCharacteristic: is called correctly and it sends a chunk (first of two) of data to the central which receives it in peripheral:didUpdateValueForCharacteristic:error: as expected.
Now there's one chunk left which is supposed to be sent in the peripheral's callback peripheralManagerIsReadyToUpdateSubscribers: according to Apple's test application.
I've tested and verified and it works fine there. It's a bit fishy though as according to the docs it's only supposed to be called when the peripheral manager's updateValue:forCharacteristic:onSubscribedCentrals: fails.
How do I make the peripheral send the remaining chunk? I can supply you with code, but it's almost identical (I'm using an array of NSData chunks instead of one large NSData like the example) to the example application I linked to, I'm more curious as to how the callback chain works and what needs to be in place for the different selectors to trigger.
What you are doing is the normal way of operation. The peripheral manager handles the data sending and implements flow control according to the current settings. E.g. if you are using indications instead of notifications, then each update has to be acknowledged by the receiver before you can send again.
Notifications on the other hand are similar to UDP packets. They can get lost. To make sure that the data arrived error free, you need to implement additional control flow management.
All in all, you are doing it right.
I managed to trigger peripheralManagerIsReadyToUpdateSubscribers: by using a loop in sendData (which is called from peripheralManagerIsReadyToUpdateSubscribers: and peripheralManager:central:didSubscribeToCharacteristic:).
- (void)sendData {
BOOL success = YES;
while (success && ([_outgoingDataQueue count] > 0)) {
NSData *chunk = [_outgoingDataQueue peek];
success = [self.peripheralManager updateValue:chunk
forCharacteristic:self.characteristic
onSubscribedCentrals:nil];
if (success) {
[_outgoingDataQueue dequeue];
}
}
}
This does not feel like the correct way to send data as chunks to the central.
I have been studying the bonjour/NSStream sample code from lecture #17 of Stanford's CS193p course (iOS programming) on iTunes U from the winter of 2010. The example code is available here.
In a nut shell, the sample code creates a socket and binds to port 0 so that it will be given a free port. It then publishes a service with that port using NSNetService (bonjour). An NSNetServiceBrowser is also started when the app starts up. Available services are placed in a UITableView. When a cell is selected, the corresponding service is resolved, an NSOutputStream is created, and data can be sent.
This is a naive implementation because connections are rejected if a connection already exists. My question is, what is the proper way to handle multiple connections? Once multiple clients are connected to the server, how does the server distinguish between them? i.e. How can data be sent specifically to one client and not the others?
- (void) _acceptConnection:(int)fd
{
int junk;
// If we already have a connection, reject this new one. This is one of the
// big simplifying assumptions in this code. A real server should handle
// multiple simultaneous connections.
if ( self.isReceiving ) {
junk = close(fd);
assert(junk == 0);
} else {
[self _startReceive:fd];
}
}
// Called by CFSocket when someone connects to our listening socket.
// This implementation just bounces the request up to Objective-C.
static void AcceptCallback(CFSocketRef s,
CFSocketCallBackType type,
CFDataRef address,
const void *data,
void *info)
{
ReceiveServer * obj;
assert(type == kCFSocketAcceptCallBack);
assert(data != NULL);
obj = (ReceiveServer *) info;
assert(obj != nil);
assert(s == obj->_listeningSocket);
[obj _acceptConnection:*(int *)data];
}
I'm not specifically familiar with that course or sample code, but: separate out the code for handling a connection into a different class from the code which accepts new connections. So, in the posted code, you'd move the -startReceive: method and everything which it calls to another class.
Then, each time a connection is accepted, create an instance of that other class. That instance will be responsible for processing all of the communication on that connection. It will be given the information about the connection (mainly the fd) during initialization. The main controller object of the server could hold those instances in an array.
Where things go from here will depend on what your server actually does. At the very least, your connection objects will need to inform the main controller object when they are closed, so the main controller can remove them from the array. You can use notifications or the delegate pattern for that.
NSData has always had a very convenient method called +dataWithContentsOfURL:options:error:. While convenient, it also blocks execution of the current thread, which meant it was basically useless for production code (Ignoring NSOperation). I used this method so infrequently, I completely forgot that it existed. Until recently.
The way I've been grabbing data from the tubes is the standard NSURLConnectionDelegate approach: Write a download class that handles the various NSURLConnectionDelegate methods, gradually build up some data, handle errors, etc. I'll usually make this generic enough to be reused for as many requests as possible.
Say my typical downloader class runs somewhere in the ballpark of 100 lines. That's 100 lines to do asynchronously what NSData can do synchronously in one line. For more complexity, that downloader class needs a delegate protocol of its own to communicate completion and errors to its owner, and the owner needs to implement that protocol in some fashion.
Now, enter Grand Central Dispatch, and I can do something as fantastically simple as:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSData* data = [NSData dataWithContentsOfURL:someURL];
// Process data, also async...
dispatch_async(dispatch_get_main_queue(), ^(void) {
// Back to the main thread for UI updates, etc.
});
});
And I can throw that sucker in anywhere I want, right in-line. No need for a download class, no need to handle connection delegate methods: Easy async data in just a few lines. The disparity between this approach and my pre-GCD approach is of a magnitude great enough to trigger the Too Good to be True Alarm.
Thus, my question: Are there any caveats to using NSData + GCD for simple data download tasks instead of NSURLConnection (Assuming I don't care about things like download progress)?
You are losing a lot of functionality here:
Can't follow the download progression
Can't cancel the download
Can't manage the possible authentication process
You can't handle errors easily, which is really important especially in mobile development like on iPhone of course (because you often lose your network in real conditions, so it is very important to track such network error cases when developing for iOS)
and there's probably more I guess.
The right approach for that is to create a class than manages the download.
See my own OHURLLoader class for example, which is simple and I made the API to be easy to use with blocks:
NSURL* url = ...
NSURLRequest* req = [NSURLRequest requestWithURL:url];
OHURLLoader* loader = [OHURLLoader URLLoaderWithRequest:req];
[loader startRequestWithCompletion:^(NSData* receivedData, NSInteger httpStatusCode) {
NSLog(#"Download of %# done (statusCode:%d)",url,httpStatusCode);
if (httpStatusCode == 200) {
NSLog(%#"Received string: %#", loader.receivedString); // receivedString is a commodity getter that interpret receivedData using the TextEncoding specified in the HTTP response
} else {
NSLog(#"HTTP Status code: %d",httpStatusCode); // Log unexpected status code
}
} errorHandler:^(NSError *error) {
NSLog(#"Error while downloading %#: %#",url,error);
}];
See the README file and sample project on github for more info.
This way:
you still rely on the asynchronous methods provided by NSURLConnection (and as the Apple's documentation says about Concurrency Programming if an API already exists to make asynchronous tasks, use it instead of relying on another threading technology if possible)
you keep advantages of NSURLConnection (error handlings, etc)
but you also have the advantages of the blocks syntax that makes your code more readable than when using delegate methods
WWDC 2010 Session Videos:
WWDC 2010 Session 207 - Network Apps for iPhone OS, Part 1
WWDC 2010 Session 208 - Network Apps for iPhone OS, Part 2
The lecturer said
"Threads Are Evilâ„¢".
For network programming, it is strongly recommended to use asynchronous API with RunLoop.
Because, if you use NSData + GCD like the following, it uses one thread per connection.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSData* data = [NSData dataWithContentsOfURL:someURL];
And it's likely to use many connections and many threads. It is too easy to use GCD :-)
Then, many threads eats huge amount of memory for its stack.
Thus, you'd better to use asynchronous API as AliSoftware said.
As of OS X v10.9 and iOS 7 the preferred way is to use NSURLSession. It gives you a nice, block-based interface and features like canceling, suspending and background downloading.
Can someone show an example on how to log in to AIM, then send and receive messages using the IMframework?
Thanks!
I am the author of an Objective-C library for AOL instant messenger. It provides a simple Object Oriented approach to instant messaging. People have used it in the past to develop iOS IM applications, and even added on to it to support things like Off-The-Record. You can check it out on github, download the source, and add the source to your application by manually copying them. Once you have the code in your project, you can sign in like this:
AIMLogin * login = [[AIMLogin alloc] initWithUsername:username password:password];
[login setDelegate:self];
if (![login beginAuthorization]) {
NSLog(#"Failed to start authenticating.");
abort();
}
After you have logged in and gotten a session, you can do things like set your status message as follows:
AIMBuddyStatus * newStatus = [[AIMBuddyStatus alloc] initWithMessage:#"Using LibOrange on Mac!" type:AIMBuddyStatusAvailable timeIdle:0 caps:nil];
[session.statusHandler updateStatus:newStatus];
[newStatus release];
You can send messages to buddies like this:
AIMBlistBuddy * buddy = [[theSession.session buddyList] buddyWithUsername:buddyName];
[theSession.messageHandler sendMessage:[AIMMessage messageWithBuddy:buddy message:#"<BODY>Hello, world!</BODY>"]];
The library supports pretty much every standard feature that AIM users experience on a day to day basis. See my working example in MyTest.m. Note that it includes things other than the core functionality, such as thread blocking detection, etc.
I know Google is using OpenAIM in gTalk. You can find out more at: http://dev.aol.com/aim
GMail: http://www.google.com/support/chat/bin/answer.py?hl=en&answer=61024