3

実用的なソリューションを実装しました。以下のコメントをご覧ください。

こんにちは、そしてこの投稿を読むために時間を割いていただきありがとうございます!

独自のAdHocWiFiネットワークをブロードキャストするハードウェアデバイスに接続するアプリを開発しています。NSStreamを使用したCFNetworkフリーダイヤルブリッジを介して、デバイスに接続し、バイトを送信し、バイトを受信することができます。私はかなり「事実上の」ストリーム開始コードを使用しており、ストリームデリゲートはNSStreamEventsを適切に報告しています。

デバイスへの接続を初期化すると、入力ストリームと出力ストリームの両方が開いていることがわかり(NSStreamEventOpenCompleted)、ハードウェアデバイスが常に「HELLO!」を送信しているため、inputStreamにすぐにBytesAvailable(NSStreamEventHasBytesAvailable)があります。

NSStreamEventHasBytesAvailableの場合、inputStreamからデータを読み取り、次のようにログに記録します。

   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;

予想どおり、デバイスが接続されている間、「LOG --devicesayed:xxxx」の一定のストリームがあります。ただし、デバイスを電源から切断すると、どのような種類のストリームイベントも受信しません。ロギングは単にすべて一緒に停止します。

私はviewDidLoadでbackgroundTimerを開始することでこの問題を解決しようとしました。これは、0.1秒ごとにinputStreamからの読み取りを試みます。読み取ることができない場合は、ブール値deviceIsConnectedがに設定されFALSE、デバイスへの接続が切断されたことをユーザーに通知するアラートを表示します。

この方法は、信頼性がかなり低く、ソケット接続の終了を検出するという一見単純なタスクを実行するための非常に不潔な方法であることが証明されています。私が正しく理解していれば、NSStreamクラスは本質的に、基盤となるBSDソケットアーキテクチャの上の「ミドルマン」または抽象化レイヤーです。

ハードウェアデバイスを電源から切断することは、デバイスのオンボードWiFiチップの範囲外を歩くことをシミュレートしています。これは「現実世界」のテストではありません。デバイスから物理的に離れているかのように、突然接続が失われることはありません。むしろ、inputStreamによって受信されたデータはゆっくりと劣化するため、デバイスが「接続済み」と「未接続」の間をジャンプすると、「ネットワークアラート」ポップアップが継続的に点滅します。

ある種のキープアライブハンドラーを実装したいのですが、iPhone / iOS/BSDソケットの経験が不足しているために深刻な障害になっています。素晴らしい個人の誰かが、ソケットが使用できなくなったことを検出して接続の再確立を試みることができる方法の基本的な例を提供できれば(タイマーで実行されている可能性があります、私はそこに正しい道を進んでいると思います!)、私は永遠に感謝するでしょう。私はグーグルを精力的に検索し、いくつかの有望なアイデアを見つけましたが、それらのどれもうまく実装することができませんでした。

CocoaASyncSocketは、私のすべての質問/フラストレーションに対する答えになるでしょうか?

この投稿をお読みいただき、ありがとうございました。私の問題と希望する解決策について明確に説明できたと思います。ご不明な点がございましたら、お気軽にお問い合わせください。できる限りお答えいたします。

4

2 に答える 2

2

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!

于 2012-06-03T20:39:15.923 に答える
1

CocoaAsyncSocketでも同じ問題が発生します(これは素晴らしいプロジェクトですが)。TCP接続は正常に機能しますが、切断は、反対側が「ルールに従って」切断した場合にのみ検出されます。回線が切断された場合(デバイスの電源を切る、範囲外になるなど)、これを検出するための何らかのメカニズムが必要です。これはクライアントのタスクです。

あなたは正しい道を進んでいNSTimerます。

これに対処する方法はいくつかありますが、主に1つのことに依存します。デバイスが(接続に成功した後)それ自体でデータを送信するか、アプリケーションがデータを要求する必要があるかです。

しかし、解決策は基本的に同じです。接続に成功したら、繰り返し可能なNSTimerを作成します。また、ある種のdataAge変数が必要になります。このタイマー(と呼ばれることもあります)は、起動するたびにconnectionMonitor増加します。dataAge

大きすぎる(> 5s)場合dataAgeは、接続(タイマーも)を破棄し、接続手順を最初からやり直します。

もちろんdataAge、デバイスからデータを取得するたびにリセットする必要があります。

また、イベントを処理する必要がありNSStreamEventErrorOccurredます。おそらく、接続手順NSStreamEventEndEncounteredを破棄して再起動します。connectionMonitor

あなたはおそらくこのチュートリアルを知っていますが、念のために:iPhoneネットワークプログラミング

于 2012-06-02T19:03:10.783 に答える