iPhone Client for Multiplayer with NSStreams

When I was learning how to show Models with OpenGL I had the great Tutorials by Jeff Lamarche to help me with. But now that I was facing the development of a client-server pattern to build a multiplayer experience for my game I was stuck, because I couldn’t find any good help here.

Maybe there are others out there who’d appreciate some clarification and sample codes about that, so I’m trying to post something that I would have liked to use, when I was starting with the Topic.

Disclaimer: I’m no apple employee and I have no formal education on iPhone Programming, these are just some experiences I’d like to share. Expect this document to change and evolve the more I learn about networking on the iPhone. The Approach works, but as long as my app isn’t finished and approved this may have to be read with caution.

The Goal

For my Multiplayer-Server I have to use-cases in mind:

  • a list of open games to join (shown in a UITableView). Here the user will be able to tap “Refresh”, similarly to most computer games
  • the actual multiplayer game, where every time the current active player changes the state, we have to forward that to the other connected players

Internet Connection Basics

First I’d like to establish some perspective on general networking issues, those interested only in the iPhone Implementation may skip this part.

Basic situation

Well yes, our users iPhone can be anywhere, in this example behind some router which somehow is able to send packages to our server.

There are those networking layers which are explained in detail everywhere and every time, e.g. here. We probably want to mess with them as few as possible and basically stick to the most abstract application layer possible.

Whats important is that our server has some fixed URL/ip-adress where the iPhone may be roaming freely and is behind the router. So it’s easy for the iPhone to call the server. The only possibility the server has to call some iPhone would be through Apples Push Notification Service. Then there are some decisions to be made:

stateless vs stateful server

Quoting Wikipedia:

A stateless server is a server that treats each request as an independent transaction that is unrelated to any previous request.

This stateless concept is all good and very useful if you have a http-server, flickr-database or twitter page where you always want to get or post definite amounts of text/images/data. (See RESTful protocols ).
But our iPhone basically sends “Hi, I’m the iPhone 3GS of User JSmith, I want to see the list of open games and want to be informed of updates to games that I might be part of!”. So we know our multiplayer-server will need knowledge of connected iPhones and has to keep those connected.
Therefore we will need a stateful connection.

TCP vs UDP

Well, I said we don’t want to go into those technical networking layers, but unfortunately for this decision we have to. Both are networking protocols that send messages using ip-protocols, but they work in a different way.

  • TCP ensures reliable shipping of our packages in the same order that we sent them. This means there’s some magic going on in the background, i.e. a lot of failure testing and accounting. Unfortunately this maybe delays our messages. Think of it as a very thoughtful reliable postman, who goes so far as to copy our messages so he can maybe ship them again, should some get lost on the way.
  • UDP makes sure your packages arrive as fast as possible, but doesn’t really care if some don’t make it to their destination in time.

Now in the usual multiplayer gaming enviroment one wants to send out each frames data as a package to all players. So speed would be most important and if a package doesn’t arrive in time one just ignores that particular frame.

In my case both for the overview of games and the ongoing multiplayer game we don’t care much for immediate package delivery as we appreciate the error handling that is done for us by tcp.

Sockets

Internet Sockets are a great way to literally lay a wired connection between two computing devices. Still you have to do some very low level configuration, and again error handling concerning disconnected devices etc. . Here Apple thought of an abstraction layer wrapped around those BSD-sockets called

NSStreams

First of all one should read the Stream Programming Guide for Cocoa for an overview on how to use those sockets. One basically has an “NSInputStream” for incoming data and an “NSOutPutStream” for data that’s supposed to be sent to the server. How I made those Streams work for me and what pitfalls I encountered will be described in the rest of this post.

The Implementation-Side

First of all I use a central object for all outbound network connections called “Communicator.h”. I could have made this a singleton and all methods class-methods. That would save the hassle of initializing it in the app-delegate and telling your objects about it. I think this is a matter of taste, I prefer to have an object around that I may release.

In order to keep this post concise I will not post all the methods and assume everyone is familiar with overloading the standard init and dealloc methods etc. I will also skip parts that are necessary for data larger then 1024 Byte and add this in a later post.

Obviously the source code doesn’t look that good on black background. Just copy it to Xcode and it will look the usual way.

Initializing the Connection

Unfortunately initiating the Connection is not as easy on the iPhone as described in the Programming Guide because
[NSStream getStreamsToHost:host port:80 inputStream:&iStream outputStream:&oStream];
is not available on Cocoa Touch (the example is for fullblown Mac/Cocoa). The lowerlevel CFNetwork framework is available though, so we may do the following:

- (void) setupConnection { 
     NSURL* serverURL=[NSURL URLWithString:@"http://serverurl.com/"];
     NSLog(@"Setting up connection to %@ : %i",[serverURL absoluteString],PORT);
     CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)[serverURL host], PORT, &readStream, &writeStream);
     if (!CFWriteStreamOpen(writeStream)) {
         NSLog(@"Error , writestream not open");
   return;
     }
     [self openStreams]; 
     NSLog(@"Streamstatus of Outputstream: %i",[outputStream streamStatus]);

     outputIdle=NO;
}

The Methods to open and close the streams look the following way:

- (void) openStreams {
   NSLog(@"OpenStreams called");
   inputStream = (NSInputStream *)readStream;
   outputStream = (NSOutputStream *)writeStream;
   [inputStream retain];
   [outputStream retain];
   [inputStream setDelegate:self];
   [outputStream setDelegate:self];
   [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
   [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
   [inputStream open];
   [outputStream open];
}

- (void) closeStreams{
   NSLog(@"CloseStreams calles");
 [inputStream close];
 [outputStream close];
 [inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
 [outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
 [inputStream setDelegate:nil];
 [outputStream setDelegate:nil];
 [inputStream release];
 [outputStream release];
 inputStream = nil;
 outputStream = nil;
}

Now in this code the NSStreams are casted from CFReadStreamRef and CFWriteStreamRef objects created by the CFStreamCreatePairWithSocketToHost-method, scheduled in the Run-Loop and opened. Now we don’t just call a write- command on the OutputStream or a read on the Input Right away, rather these objects call us when they are ready to receive data or have data available. Thats why they have to be scheduled in the RunLoop. That happens in a similar way the User Interface calls the code when the user taps something.

Therefore at first a message that comes in from another object in my project is added to an NSArray msgStack and not immediately written to the OutputStream. This is done in the following method

- (void) msgToServer:(NSString*)msg RespondingObject:(id<MsgWaiter>)object WaitingForKeyword:(NSString*)key{
      [msgStack addObject:msg];
      NSLog(@"Message '%@' added, size of msgStack is %i",msg,[msgStack count]);
      if (object!=nil) [objectsThatAwaitAnswer setObject:object forKey:key];
      [self tellAboutNewOutMsg];
}

This looks a little bit complicated but is not that bad. Some “object” in the app calls the “communicator” to send “msg” to the server. If it awaits an answer it gives a pointer to itself, if it doesn’t it just sends “nil”. The “tellAboutNewOutMsg”-methods just sets a flag a new message is about to be sent, I will talk about this at the end of the post. To ensure the sending object is really able to handle an answer it has to conform to the “MsgWaiter” protocol:

@protocol MsgWaiter
- (void) handleServerMessage: (NSDictionary*)dict;
@end

The method that I need to implement in the communicator to facilitate Stream-Events then looks like this:

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
  NSLog(@"stream:handleEvent: is invoked...");
 
  switch(eventCode) {
          case NSStreamEventHasSpaceAvailable:
                   if (stream == outputStream) {
                        if ([msgStack count]>0) {
                             [self writeToOutput:[msgStack objectAtIndex:0]];
                             [msgStack removeObjectAtIndex:0];
                        }
                   else outputIdle=YES;
            }
          break;
            case NSStreamEventHasBytesAvailable:
                   if (stream== inputStream) {
                        NSLog(@"inputStream has bytes available"); 
                        uint8_t buf[1024];
                        unsigned int len = 0;
                        len = [inputStream read:buf maxLength:1024];
                        if (len>0) {
                             NSMutableData* data=[[NSMutableData alloc] initWithLength:0];
                             [data appendBytes: (const void *)buf length:len];
                             NSString *s = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
                             [self handleInput:s];
                             [data release];
                        }
                   } 
                   break;
         default:
         {
               NSLog(@"Stream is sending an Event: %i",eventCode);
               break;
         }
  }
}

Now once “handleEvent” has determined the OutputStream to have Space available, “writeToOutput” is called for the top element of the msgStack and the message is removed from the stack.

- (void) writeToOutput:(NSString*)s {
     uint8_t* buf=(uint8_t*)[s UTF8String];
     [outputStream write:buf maxLength:strlen((char*)buf)];  
}

Should the server send some data back, the InputStream calls us with the event “NSStreamEventHasBytesAvailable”. My code looks more complicated than needed here, because I wrap a NSMutableDictionary around the recieved string. This part will later be modified to cope with JSON-Strings following the starting keyword, which will then be added as Objects to the NSDictionary. (Keep in mind I don’t care about data that is larger than 1kB yet!). Then the string that came from the server is processed in “handleInput”:

- (void) handleInput:(NSString*)string {
     NSLog(@"Got Message: %@ \n", string);
     NSString* start=[string substringToIndex:4]; 
     id<MsgWaiter> object=[objectsThatAwaitAnswer objectForKey:start];
     if (object!=nil) {
          NSDictionary* answerDict=[[NSDictionary alloc] initWithObjectsAndKeys:string,@"type",nil];
          [object handleServerMessage:answerDict];
          [answerDict release];
          [objectsThatAwaitAnswer removeObjectForKey:start];
     }
     else {
           NSLog(@"Received Message but no object registered that awaits %@",start);
     }
     [string release];
}

Well I thought the first 4 Characters of a message should be some Keyword that tells my communicator what to do with this message. Here we also see why all responding objects were forced to conform to the “MsgWaiter” protocol, we just want to be sure they are able to handle incoming messages.

Another Problem I encountered: Idle Outputstream

I was really wondering why I couldn’t send any second messages after the first, everything was fine, it just didn’t call “handleEvent” anymore. A Outputstream that fires “NSStreamEventHasSpaceAvailable” but doesn’t get any data doesn’t fire again. It tells you it has Space available and then goes to sleep happily, knowing it now has time to slack off since it obviously isn’t needed. So I had to put in a flag to record whether the “outputstream” is idle and just write to it immediately.

- (void) tellAboutNewOutMsg {

      if (outputIdle) { 

           [self writeToOutput:[msgStack objectAtIndex:0]];

           [msgStack removeObjectAtIndex:0];

           outputIdle=NO;

      }

}

I hope someone out there finds those snippets of code useful. I know I would have, sadly I can’t send them back into the past 🙂

Update:

Keep in mind the code posted above doesn’t try to connect again, should the “CFStreamCreatePairWithSocketToHost” not be successful. I added a flag “(BOOL)socketConnected” that is set to “YES” once the first answer from the server arrives. Then when “msgToServer” is called and the flag is “NO” the communicator tries “CFStreamCreatePairWithSocketToHost” again.
Once this initial connection worked the streams can be kept open for the rest of the apps lifetime.

Update 2:

I realized in case of a very unstable network connection (maybe a wireless access point is far away) tcp may not be absolutely reliable. This means in rare occasions a packet may be lost or broken. For me I worked around this issue by catching exceptions around the message recieve part to discover broken packages and request them again. For the packages that change hand the control of the action from one iPhone to another the iPhone that hands over control transmits the message over and over again as long as it gets no confirmation. Remember my game is a turn-based one, so should the message of player 1 that says he is finished never arrive at player 2 the whole thing would deadlock!

Advertisements

16 Responses to “iPhone Client for Multiplayer with NSStreams”

  1. Wow, this just saved me a night of banging my head against the screen. Thank you!

    Are you keeping the same two stream instances alive&open throughout this code?

    • Yes, both streams are active for the whole time the app is connected and are only initialized once.

      But keep in mind the code posted above doesn’t try to connect again, should the “CFStreamCreatePairWithSocketToHost” not be successful. I added a flag “(BOOL)socketConnected” that is set to “YES” once the first answer from the server arrives. Then when “msgToServer” is called and the flag is “NO” the communicator tries “CFStreamCreatePairWithSocketToHost” again.
      But once this initial connection worked the streams can be kept open for the rest of the apps lifetime.

  2. […] Client for Multiplayer with NSStreams – Part 2 In the first version of the communicator class we were only handling messages that were smaller then 1024 […]

  3. It looked like an interesting tutorial but I couldn’t read most of the code. It’s dark blue and purple text on black background… :/

    Nick

    • Hey there, the easiest way is to copy the code into Xcode so you take advantage of the syntax highlighting.
      I couldn’t find any decent syntax-highlighting plugin fo wordpress and ended up taking roughly the colors xcode uses, just modified for a black background.

  4. Thank you very much for the tutorial ;D If it is not much trouble, can you also attach plain source code of this example?

  5. you never release readStream and writeStream

  6. I have built a python server for a client similar to yours. I did not succeed to connect the app running on the device to the server (works on the simulator). Probably this is due to some settings in the router. Any suggestion?

    • Did you open the respectable port your server software, e.g. apache?
      I use nginx and had to edit the “nginx.conf” to open that port. Maybe you need to look for tutorials how to do that on your architecture.

  7. I have solved in another way. Thanks for replying.

  8. great tutorial very helpful! im new to iphone programming and im trying to connect my iphone app to a cSharp server i build. so i have some questions:
    1. i dont understand what is the different between using NSStream and CFNetwork?
    2. why are you doing the casting (NSOutputStream *)writeStream; ?
    why not just using CFWriteStreamRef ?
    3. if you want to use NSStreams from the start why not using
    [NSStream getStreamToHost] instead of CFStreamCreatePairWithSocketToHost?
    thanks!

  9. I wanted to take the time and say thank you for posting this. I got to the point where the stream delegate for NSOutputStream goes idle once we get the NSStreamEventHasSpaceAvailable event.

    For the input stream I use a while loop for data larger than 1024:

    while ( [(NSInputStream *)stream hasBytesAvailable] ) {}

    This is inside the case NSStreamEventHasBytesAvailable:

    I am working on doing the same thing for the NSOutputStream and can post back if it works.

    —-

    A few differences in what I’m doing as the HTTP Server is that I’m using this function to create the stream pair: CFStreamCreatePairWithSocket(). This occurs in the socket callback when the type is: kCFSocketAcceptCallBack. Flags I think are too overrated and I try to avoid them when possible. So if the streams are created then I just send the stuff to a delegate and let it go. If they are not created then the delegate never gets notified.

    The other difference is that my input and output streams are on different threads with their own runloop. I found that having them on the same runloop can produce some weird results. So thought it was best to separate the children. Not to keep them in isolation I needed them to talk to one another. Well NSConnection to the resuce. I init one of these for each thread and we have some happy children. It’s working great so far.

    Thanks again.

  10. 7 osi layers…

    […]iPhone Client for Multiplayer with NSStreams « Developing Tactica[…]…

  11. get back clients…

    […]iPhone Client for Multiplayer with NSStreams « Developing Tactica[…]…

  12. Andrey Says:

    TCP is reliable but there’s error in the code which can lead to broken packets when connection is unstable.

    Read returns data in chunks. Do not parse it right away. Pass packet length first, ensure you receive it completely: if it is uint32_t, call read multiple times until all 4 bytes are read. Then receive data of this length (also call read multiple times).

    Same applies to write. It can write less bytes than requested. In this case you should call it again for remaining data until no more data is left.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: