IT tutorials
 
Mobile
 

iPhone SDK 3 : Making Connections with GameKit and Bonjour - iPhone to iPhone Gaming Via BonjourHelper

3/4/2013 6:39:40 PM
- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire

If you’re willing to forgo GameKit’s Bluetooth and work with WiFi, you can duplicate many of GameKit’s features on all iPhones including the older first generation units. Recipe 1 introduces BonjourHelper. 

[GameKitHelper sharedInstance].sessionID = @"Typing Together";
[GameKitHelper sharedInstance].dataDelegate = self;
[GameKitHelper assignViewController:self];

Substituting BonjourHelper for GameKitHelper requires very few programming changes. It uses the same initialization steps, and the data delegate receives an identical set of callbacks. You do need to omit the space in the session ID, a step that isn’t needed in GameKit. GameKit encrypts its session IDs to produce a guaranteed no-space proper Bonjour identifier. BonjourHelper’s plain-text approach means spaces are off-limits. Limit your session ID names to simple alphanumeric text with 14 characters or fewer. Refer to RFC 2782 service types (http://www.dns-sd.org/ServiceTypes.html) for details. The BonjourHelper code transforms the session ID into a standard Bonjour identifier (i.e., _typingtogether._tcp.).

[BonjourHelper sharedInstance].sessionID = @"TypingTogether";
[BonjourHelper sharedInstance].dataDelegate = self;
[BonjourHelper assignViewController:self];

That’s not to say that the functionality and implementation are identical. With BonjourHelper, both units must be on the same network. You lose the pretty GameKit peer connection controller sequence . Instead, BonjourHelper provides a simple alert, as shown in Figure 12-8. Beyond that, BonjourHelper basically provides the same peer-to-peer connectivity and data flow as GameKit.

Figure 1. The custom BonjourHelper class provides a simpler connection interface than GameKit.


Registering Bonjour Names and Ports

You should register any Bonjour names you plan to use for commercial release with the DNS Service Discovery organization. Registration ensures that your service names and protocols will not overlap or conflict with any other vendor. A list of currently registered services is maintained at http://www.dns-sd.org/ServiceTypes.html.

These names must conform to the RFC 2782 standard. Submit your protocol name to srv_type_request@dns-sd.org. Include the up-to-14-character name of the Bonjour service, a longer descriptive name, the contact information (name and e-mail address) of the person registering the service, and an information page URL. Specify the transportation protocol (i.e., _tcp or _udp) and a list of any TXT record keys used.

It may take some time for the volunteers at the dns-sd.org site to process and respond to your query. Delays on the order of weeks are not uncommon. You may need to resubmit, so keep a copy of all your information.

If you plan to use a fixed port (most Bonjour implementations randomly pick a port at runtime to use), you’ll want to submit an application for a registered port number with IANA, the Internet Assigned Numbers Authority, as well. IANA provides a central repository for port registrations and will, at some time, be merged with the dns-sd registry. IANA often takes a year or longer to finish registering new protocol port numbers.

Note

Apple maintains a list of official OS X Bonjour service types in its Technical Q&A QA1312 document, which you can find at http://developer.apple.com/mac/library/qa/qa2001/qa1312.html.


Duplex Connection

For simplicity, BonjourHelper works by establishing a duplex connection. Each device provides both a client and a host. This avoids any issues about trying to get two peers to negotiate with each other and assume the proper server and client roles without both of them ending up as client or server at the same time.

When resolving addresses, the helper ensures that the unit will not connect to itself. It demands a unique IP address that doesn’t match the local one. If the incoming address does match, it just continues looking. The host needs no such checks; outgoing client connections are limited to foreign addresses.

When the helper has established an outgoing connection and accepted an incoming one, it stops looking for any further peers and considers itself fully connected. The helper updates the Connect/Disconnect button if a view controller has been set.

Reading Data

Recipe 1 cannot use a simple read loop, that is, request data, read it, and repeat. Reading data is blocking. A read loop prevents an application from handling its server duties at the same time as its client duties.

Instead, this class uses the nonblocking hasDataAvailable check before asking for new data. A delayed selector adds a natural interval into the poll allowing each host time to update and prepare new data before being barraged by a new request.

Closing Connections

Connections can break in several ways. Users can quit an application, they can press the Disconnect button in the sample, or they can move out of range of the connection. BonjourHelper checks for disconnects exclusively from the server point of view. This simplifies its implementation, assuming that a lost client equates to a lost host and avoids the issue of multiple user notifications, i.e., “Lost connection to server” and “Lost connection to client” for both ends of the duplex connection.

Note

For space considerations, this listing of Recipe 1 omits a number of basic IP utilities, including stringFromAddress:, addressFromString:address:, and localIPAddress. 


Recipe 1. BonjourHelper Provides GameKit-like Connectivity over WiFi
#define DO_DATA_CALLBACK(X, Y) if (sharedInstance.dataDelegate && \
    [sharedInstance.dataDelegate respondsToSelector:@selector(X)]) \
    [sharedInstance.dataDelegate performSelector:@selector(X) \
    withObject:Y];
#define BARBUTTON(TITLE, SELECTOR)     [[[UIBarButtonItem alloc] \
    initWithTitle:TITLE style:UIBarButtonItemStylePlain \
    target:[BonjourHelper class] action:SELECTOR] autorelease]
@implementation BonjourHelper
@synthesize server;
@synthesize browser;
@synthesize inConnection;
@synthesize outConnection;

@synthesize dataDelegate;
@synthesize viewController;
@synthesize sessionID;
@synthesize isConnected;
@synthesize hud;

static BonjourHelper *sharedInstance = nil;
BOOL inConnected;
BOOL outConnected;

+ (BonjourHelper *) sharedInstance
{
    if(!sharedInstance) sharedInstance = [[self alloc] init];
    return sharedInstance;
}

#pragma mark Class utilities
+ (void) assignViewController: (UIViewController *) aViewController
{
    // By assigning the optional view controller, this class
    // takes charge of the connect/disconnect button
    sharedInstance.viewController = aViewController;
    if (sharedInstance.viewController)
        sharedInstance.viewController.navigationItem.rightBarButtonItem
        = BARBUTTON(@"Connect", @selector(connect));
}

#pragma mark Handshaking

- (void) updateStatus
{
    // Must be connected to continue
    if (!(self.inConnection && self.outConnection) ||
        !(inConnected && outConnected))
    {
        self.isConnected = NO;
        return;
    }

    // Send callback, dismiss HUD, update bar button
    self.isConnected = YES;
    DO_DATA_CALLBACK(connectionEstablished, nil);
    [self.hud dismissWithClickedButtonIndex:1 animated:YES];
    if (self.viewController)
        self.viewController.navigationItem.rightBarButtonItem =
            BARBUTTON(@"Disconnect", @selector(disconnect));
}

// Upon resolving address, create a connection to that address
// and request data
- (void)netServiceDidResolveAddress:(NSNetService *)netService
{
    NSArray* addresses = [netService addresses];
    if (addresses && addresses.count)
    {
        for (int i = 0; i < addresses.count; i++)
        {
            // The IP utility implementations can be found in
           // They are omitted here for space considerations.
            struct sockaddr* address =
                (struct sockaddr*)[[addresses objectAtIndex:i] bytes];
            NSString *addressString =
                [BonjourHelper stringFromAddress:address];
            if (!addressString) continue;

            if ([addressString hasPrefix:
                    [BonjourHelper localIPAddress]])
            {
                printf("Will not resolve with self. \
                    Continuing to browse.\n");
                continue;
            }

            printf("Found a matching external service\n");
            printf("My address: %s\n",
                [[BonjourHelper localIPAddress] UTF8String]);
            printf("Remote address: %s\n", [addressString UTF8String]);

            // Stop browsing for services
            [self.browser stop];
            [netService release];

            // Create an outbound connection to this new service
            self.outConnection = [[[TCPConnection alloc]
                initWithRemoteAddress:address] autorelease];
            [self.outConnection setDelegate:self];
            [self performSelector:@selector(checkForData)];
            [self updateStatus];
            return;
        }
    }

    [netService stop];
}

- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
    didFindService:(NSNetService *)netService
    moreComing:(BOOL)moreServicesComing
{
    // start to resolve the service that was found
    [[netService retain] setDelegate:self];
    [netService resolveWithTimeout:0.0f];
}

+ (void) startBrowsingForServices
{
    // look for matching Bonjour services. The double-retain was
    // added for security. You can almost certainly discard it.
    sharedInstance.browser =
        [[[NSNetServiceBrowser alloc] init] retain];
    [sharedInstance.browser setDelegate:sharedInstance];
    NSString *type = [TCPConnection
        bonjourTypeFromIdentifier:sharedInstance.sessionID];
    [sharedInstance.browser searchForServicesOfType:type
        inDomain:@"local"];
}

+ (void) publish
{
    // Publish service to peers
    sharedInstance.server =
        [[[TCPServer alloc] initWithPort:0] autorelease];
    [sharedInstance.server setDelegate:sharedInstance];
    [sharedInstance.server startUsingRunLoop:
        [NSRunLoop currentRunLoop]];
    [sharedInstance.server enableBonjourWithDomain:@"local"
        applicationProtocol:sharedInstance.sessionID
        name:[self localHostname]];
}

+ (void) initConnections
{
    // Return to base unconnected state
    [sharedInstance.browser stop];
    [sharedInstance.server stop];

    sharedInstance.inConnection = nil;
    sharedInstance.outConnection = nil;
    sharedInstance.isConnected = NO;
    inConnected = NO;
    outConnected = NO;
}

- (void)alertView:(UIAlertView *)alertView
    clickedButtonAtIndex:(NSInteger)buttonIndex
{
    // Handle user request to cancel connecting
    if (buttonIndex) return;
    [BonjourHelper disconnect];
}

+ (void) connect
{
    if (sharedInstance.viewController)
        sharedInstance.viewController.navigationItem.rightBarButtonItem
            = nil;
    if (!sharedInstance.sessionID)
        sharedInstance.sessionID = @"Sample Session";

    // Create activity view with cancel button
    sharedInstance.hud = [[[UIAlertView alloc]
        initWithTitle:
        @"Searching for connection peer on your local network"
        message:@"\n\n" delegate:sharedInstance
        cancelButtonTitle:@"Cancel" otherButtonTitles:nil]
        autorelease];
    [sharedInstance.hud show];

    // Add the progress wheel
    UIActivityIndicatorView *aiv = [[[UIActivityIndicatorView alloc]
        initWithActivityIndicatorStyle:
        UIActivityIndicatorViewStyleWhiteLarge] autorelease];
    [aiv startAnimating];
    aiv.center = CGPointMake(
        sharedInstance.hud.bounds.size.width / 2.0f,
        sharedInstance.hud.bounds.size.height/2.0f);
    [sharedInstance.hud addSubview:aiv];

    // Prepare for duplex connection
    [self initConnections];
    [self startBrowsingForServices];
    [self publish];
}

+ (void) disconnect
{
    // disable current connections
    [sharedInstance.inConnection invalidate];
    [sharedInstance.outConnection invalidate];
    [self initConnections];

    // stop server
    [sharedInstance.server stop];
    [sharedInstance updateStatus];

    // reset
    [sharedInstance.hud dismissWithClickedButtonIndex:1 animated:YES];
    if (sharedInstance.viewController)
        sharedInstance.viewController.navigationItem.rightBarButtonItem
            = BARBUTTON(@"Connect", @selector(connect));
}

#pragma mark  Data Handling
- (void) checkForData
{
    // Perform a blocking receive only when data is available
    if (!self.outConnection) return;
    if ([self.outConnection hasDataAvailable])
        [self.outConnection receiveData];
    [self performSelector:@selector(checkForData)
        withObject:self afterDelay:0.1f];
}

+ (void) sendData: (NSData *) data
{
    if (!sharedInstance.outConnection) return;
    BOOL success = [sharedInstance.outConnection sendData:data];
    if (success) {
        DO_DATA_CALLBACK(sentData:, nil); }
    else {
        DO_DATA_CALLBACK(sentData:, @"Data could not be sent.");}
}

- (void) connection:(TCPConnection*)connection
    didReceiveData:(NSData*)data;
{
    // Redirect data callback
    DO_DATA_CALLBACK(receivedData:, data);
}

#pragma mark Connection Handlers

- (BOOL) server:(TCPServer*)server
    shouldAcceptConnectionFromAddress:(const struct sockaddr*)address
{
    // Accept connections only while not connected
    return !self.isConnected;
}

- (void) connectionDidFailOpening:(TCPConnection*)connection
{
    // Handled a fail open
    if (!connection) return;
    NSString *addressString = [BonjourHelper
        stringFromAddress:connection.remoteSocketAddress];
    [BonjourHelper disconnect];

    if (addressString)
        [ModalAlert say:@"Error while opening %@ connection (from %@).\
            Wait a few seconds or relaunch before trying to connect\n
            again.", (connection == self.inConnection) ? @"incoming" :
            @"outgoing", addressString];
    else
        printf("Failed to open connection from unknown address\n");
}

- (void) server:(TCPServer*)server
    didCloseConnection:(TCPServerConnection*)connection
{
    // Handle a newly closed connection
    if (!connection) return;
    NSString *addressString = [BonjourHelper
        stringFromAddress:connection.remoteSocketAddress];
    if (!addressString) return;

    BOOL wasConnected = self.isConnected;

    [BonjourHelper disconnect];
    printf("Lost connection from %s\n", [addressString UTF8String]);

    if (wasConnected)
        [ModalAlert say:@"Disconnected from peer (%@). You are no \
            longer connected to another device.", addressString];
    else
        [ModalAlert say:@"Peer was lost before full connection could \
            be established."];
}

- (void) server:(TCPServer*)server
    didOpenConnection:(TCPServerConnection*)connection
{
    // Set the connection but wait for it to fully open
    self.inConnection = connection;
    [self updateStatus];
    [connection setDelegate:self];
}

- (void) connectionDidOpen: (TCPConnection *) connection
{
    // Fully opened connection
    printf("Connection did open: %s\n", (connection ==
        self.inConnection) ? "incoming" : "outgoing");
    if (connection == self.inConnection) inConnected = YES;
    if (connection == self.outConnection) outConnected = YES;
    [self updateStatus];
}

- (void) connectionDidClose: (TCPConnection *)connection
{
    // Closed connection
    printf("Connection did close: %s\n", (connection ==
        self.inConnection) ? "incoming" : "outgoing");
    if (connection == self.inConnection) inConnected = NO;
    if (connection == self.outConnection) outConnected = NO;
    [self updateStatus];
}
@end			
 
Others
 
- iPhone SDK 3 : Making Connections with GameKit and Bonjour - Working Around Real-World GameKit Limitations
- Android : Getting Fancy with Lists - Interactive Rows
- Android : Getting Fancy with Lists - Better. Stronger. Faster.
- Windows Phone 7 : Designing the Game Framework (part 3) - The GameHost Class
- Windows Phone 7 : Designing the Game Framework (part 2) - The TextObject Class
- Windows Phone 7 : Designing the Game Framework (part 1) - The GameObjectBase Class, The SpriteObject Class
- Windows Phone 7 : Getting Started with XNA - Other Graphics Options
- iPad : Your Calendar - Adding New Calendar Appointments, Events (part 2)
- iPad : Your Calendar - Adding New Calendar Appointments, Events (part 1)
- Java ME on Symbian OS : Handling Diversity - Using Adaptive Code and Flexible Design to Handle Diversity
 
 
Top 10
 
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
programming4us programming4us
 
Popular tags
 
Video Tutorail Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Biztalk Exchange Server Microsoft LynC Server Microsoft Dynamic Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Indesign Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe After Effects Adobe Photoshop Adobe Fireworks Adobe Flash Catalyst Corel Painter X CorelDRAW X5 CorelDraw 10 QuarkXPress 8 windows Phone 7 windows Phone 8 BlackBerry Android Ipad Iphone iOS