Domanda

I have implemented a working solutions, please see my comment below

Hello, and thank you for taking the time to read this post!

I'm developing an app which connects to a hardware device which broadcasts its own AdHoc WiFi network. I am able to connect to the device, send bytes, and receive bytes via CFNetwork toll-free bridging with NSStream. I am using the rather "de-facto" stream opening code and the stream delegate is reporting NSStreamEvents as it should.

Upon initializing my connection to the device, I can see both input and output streams open (NSStreamEventOpenCompleted) and as the hardware device is constantly sending "HELLO!", there are immediately BytesAvailable on the inputStream (NSStreamEventHasBytesAvailable).

In the case event for NSStreamEventHasBytesAvailable, I read data from the inputStream and log it as so:

   case NSStreamEventHasBytesAvailable:
        NSLog(@"CASE LOG -- NSStreamEventHasBytesAvailable");
        uint8_t buffer[256];
        int len;

        while ([inputStream hasBytesAvailable]) {
            //NSLog(@"LOG -- inputStream hasBytesAvailable");
            len = [inputStream read:buffer maxLength:sizeof(buffer)];
            if (len > 0) {

                NSLog(@"Length of inputStream Bytes -- %i",len);
                NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];

                // This global boolean affects the rest of the apps functionality. Buttons are not able to send bytes to the hardware device if this boolean is FALSE.                    
                deviceIsConnected = true;

                // If buttons to send bytes are disabled due to lack of network connection on appLaunch, then go ahead and show them, allowing the user to send bytes to the hardware device
                if(buttonsAreDisabled == true)
                {
                    [ self setButtonVisibility:true ];

                    // Reset the status of the "No Connection" Alert
                    connectionAlertShown = false;
                }

                // Log the incoming data
                if (nil != output) {
                     NSLog(@"LOG -- device said: %@", output);
                }
            }
        }
   break;

As expected, I have a constant stream of "LOG -- device said: xxxx" while my device is connected. However, if I disconnect the device from its power source, I do not receive any sort of Stream Event; the logging simply stops all together.

I have attempted to remedy this issue by starting a backgroundTimer in my viewDidLoad, which every 0.1 seconds attempts to read from the inputStream. If it is not able to read, then the boolean deviceIsConnected is set to FALSE and I display an alert informing the user that their connection to the device has dropped.

This method has proven rather unreliable, and also a very ungraceful way of going about a seemingly simple task of detecting the closing of a socket connection. If I understand correctly, the NSStream class is essentially a "Middle man" or abstraction layer above the underlying BSD Socket Architecture.

Disconnecting the hardware device from its power source is simulating walking out of range of the device's onboard WiFi chip. This is not a "real world" test, as if you are physically walking away from the device, you will not suddenly lose connection; rather, the data received by the inputStream will slowly deteriorate thus causing the "Network Alert" popup to continuously flicker as the device jumps between "connected" and "not connected".

I would like to implement some sort of KeepAlive handler, but my lack of experience with iPhone / iOS / BSD Sockets is hindering me severely. If any of you wonderful individuals could provide a basic example of a method (likely running on a timer, I think I'm on the right path there!) that can detect a socket becoming unavailable and proceed to attempt to reestablished the connection, I would be eternally grateful. I have tirelessly searched Google and found a few promising ideas, but haven't been able to successfully implement any of them.

Could CocoaASyncSocket be the answer to all my questions / frustrations?

Thank you again for taking the time to read over this post. I hope I have provided a clear explanation of my problem and my desired solution. Should you have any questions, please feel free to ask and I will do my best to answer.

È stato utile?

Soluzione

My previously explained theory (see comments) has indeed worked. I am now able to successfully monitor the status of the device connectivity with the following logic (please understand that the following chunks of code exist throughout the application). I am able to determine the exact moment the device becomes unavailable; be it due to lack of WiFi connectivity, or the device losing power.

// Define two socket objects, a "Main" socket and "Observer" socket

asyncSocketMain = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
dispatch_queue_t secondaryQueue = dispatch_get_current_queue();
asyncSocketObserver = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:secondaryQueue];

// On application launch, attempt to open asyncSocketMain
[asyncSocketMain connectToHost:host onPort:port withTimeout: 2.0 error:&error]

// Determine which socket object is connecting in the didConnectToHost method
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
NSString *currentSocketId = (NSString *)sock;

// Determine Which Socket Object is Connecting...
if(currentSocketId == (NSString *)asyncSocketMain)
{
    NSLog(@"Main Socket has Connected!");
    connectionIsOpening = false;  // Allow for Future Reconnect Attempts
    deviceIsConnected   = true;   // Allow Connection Monitoring

    // If the Main Socket has been attempting to reconnect, stop doing that!
    if(reconnectTimer)
    {
        [ reconnectTimer invalidate ]; // Stop the reconnectTimer
        reconnectTimer = nil;          // And also set its value to nil
    }
    [ self setupMonitorTimer ];   // Begin Monitoring the Connection
}else{
    if(currentSocketId == (NSString *)asyncSocketObserver)
    {
        NSLog(@"Observer Socket attempting connection! Socket: %@", sock);
    }else{
        NSLog(@"ALERT ALERT -- UNKNOWN SOCKET CONNECTING!!!");
    }
}
} // close void

Now, when the observer socket attempts to connect, an error will be thrown. This is how I am able to determine current connectivity. The observer will always throw error code 7 if the asyncSocketMain socket is already connected. If asyncSocketObserver times out when attempting to connect, that means the device is either powered off, out of range, or otherwise unavailable (e.g. the users phone is not connected to the correct WiFi network). In this case, all "monitoring" should be halted, and a timer is started for asyncSocketMain to attempt reconnects.

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{


NSString *connectingSocket = (NSString *)sock;

// Figure out hte Error Code Info  
NSError * error;
error = err;
NSInteger errorNum = error.code;


if(connectingSocket == (NSString *)asyncSocketMain)
{
    // This may occur if the app is opened when the device is out of range
    NSLog(@"The MAIN SOCKET Encountered an Error while Connecting! [ CODE: %d ]", errorNum);
    [ asyncSocketMain disconnect ]; // Disconnect the Main Socket to allow for reconnects
    connectionIsOpening = false;    // Allow Main Connection Attempts
    deviceIsConnected = false;      // Device is NOT CONNECTED -- Do NOT RUN MONITORING
    if(!reconnectTimer)
    {
        NSLog(@"Starting the reconnectTimer");
        [ self setupReconnectTimer ];   // Start attempting to reconnect
    }else{
        NSLog(@"Reconnect Timer is Already Running!");
    }
}
else
if(connectingSocket == (NSString *)asyncSocketObserver)
{
    switch (errorNum)
    {
        case 1:
            // Not much to do here...
            NSLog(@"OBSERVER ERROR - There is already a socket attempting to connect!");
            break;

        case 2:
            // Not much to do here...
            NSLog(@"OBSERVER ERROR - Event 2");
            break;

        case 3:
            // Time Out -- The device is out of range. Halt observer connection attempts, disconnect the main
            // socket object, then proceed to attempt to reconnect with the main socket.
            NSLog(@"OBSERVER ERROR - Connected Timed out -- Device not available!!!!");

            // The Observer Socket Timed out -- It's time to start reconnecting
            connectionIsOpening = false; // Allow Main Connection Attempts
            deviceIsConnected   = false; // Device is NOT CONNECTED - DO NOT RUN MONITORING and ALLOW CONNECTION ATTEMPTS
            if(monitorTimer)
            {
                // Stop trying to reconnect with the observer socket, thus allowing the Main socket to connect
                NSLog(@"Stopping the Monitoring Method...");
                [monitorTimer invalidate];
                monitorTimer = nil;
            }else{
                NSLog(@"Connection Monitoring already halted!");
            }

            // If the reconnectTimer is not running (it shouldnt be, otherwise something is wrong) then go ahead and run it
            // This will attempt to reconnect asyncSocketMain
            if(!reconnectTimer)
            {
                NSLog(@"Starting the reconnectTimer");
                [ asyncSocketMain disconnect ]; // Deallocate the main socket to allow for reconnects
                [ self setupReconnectTimer ];
            }else{
                NSLog(@"Reconnection Attempts are already happening! [ reconnectTimer: %@ ]",reconnectTimer);
            }

            break;

        case 7:
            NSLog(@"OBSERVER ERROR - The Main Socket is Already Connected!");
            break;
    }
}
else{
    NSLog(@"An Unknown Socket Connection Encountered and Error...");
}
} // end void

For reference, I write all data out on asyncSocketMain. The asyncSocketObserver object is always attempting to connect on a timer, whenever deviceIsConnected = TRUE.

This may not be the most graceful way of monitoring the connection, but it does indeed work. As soon as I disconnect power from my device, asyncSocketObserver times out, which then (as per code) halts all "connection monitoring" and beings a "reconnect" timer, which is then invalided as soon as connection is established.

Thank you again @rokjarc for your helpful knowledge and input, I hope the semi-pseudo code I have provided here (which does indeed function as intended!) aids some other developer, as this is something that I have struggled with a for at least a week!

Altri suggerimenti

You'd have the same problem with CocoaAsyncSocket (though it is a great project). TCP connections works normally but disconnection is detected only if the other side disconnects 'by the rules'. If a line is broke (turning off device, going out of range...) you need some mechanism to detect this. This is clients task.

You are on right path thinking about using NSTimer.

There are several ways to deal with this, mostly depending on one thing: is your device sending data by itself (after successful connection) or does your application have to request data.

But solution is basically the same. After making successful connection you create a repeatable NSTimer. You'll also need some kind of dataAge variable. This timer (could be called connectionMonitor) increases dataAge each time it fires.

If dataAge is too big (>5s) you destroy the connection (also the timer) and start connecting procedure all over.

Of course dataAge should be reset each time you get data from the device.

You should also handle events NSStreamEventErrorOccurred and NSStreamEventEndEncountered: probably with destroying connectionMonitor and restarting connection procedure.

You probably know this tutorial but just in case: iPhone Network Programming

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top