1

おおよそ次の手順に従う iOS アプリケーションがあります。

  1. リスニング ソケットを開きます。
  2. 単一のクライアント接続を受け入れます。
  3. クライアントとのデータ交換を実行します。
  4. 「resign active」イベントを受け取ると、クライアントとサーバーのソケットに関連付けられたすべてのリソースを閉じて解放します (つまり、すべての実行ループ ソース、読み取り/書き込みストリーム、およびソケット自体を無効にして解放します)。
  5. アクティブを再開すると、リスニング ソケットを元に戻して通信を継続します (クライアントは、手順 4 で iOS アプリがアクティブを辞退した後、再接続できるようになるまで再接続を試み続けます)。

クライアントとサーバーの間で接続が行われるたびに、ステップ 5 の後に、リッスンのためにサーバー ソケットを再度開くことができずにアプリケーションが再開されることがわかります。つまり、手順 5 ですべてが解放されても、アプリケーションは再バインドしてソケット アドレスをリッスンすることができません。さらに悪いことに、リッスン ソケットを再度セットアップしようとしても、CFSocket API 呼び出しでエラーが検出されません。

一方、iOS アプリケーションがアクティブを辞退し、以前に接続を受信せずに再び再開した場合、クライアントは、アプリケーションが辞任して再び再開するまで、一度だけ接続できます。この場合、上記と同じ動作が観察されます。 .

この問題を説明する最小限のアプリケーションの例は、次のリポジトリにあります。

https://github.com/dpereira/cfsocket_reopen_bug

最も関連性の高いソースは次のとおりです。

#import "AppDelegate.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>

static void _handleConnect(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void* data, void* info)
{
    NSLog(@"Connected ...");
    close(*(CFSocketNativeHandle*)data);
    NSLog(@"Closed ...");
}

@interface AppDelegate ()

@end

@implementation AppDelegate {
    CFRunLoopSourceRef _source;
    CFSocketRef _serverSocket;
    CFRunLoopRef _socketRunLoop;
}

- (void)applicationWillResignActive:(UIApplication *)application {
    
    CFRunLoopRemoveSource(self->_socketRunLoop, self->_source, kCFRunLoopCommonModes);
    CFRunLoopSourceInvalidate(self->_source);
    CFRelease(self->_source);
    self->_source = nil;
    
    CFSocketInvalidate(self->_serverSocket);
    CFRelease(self->_serverSocket);
    self->_serverSocket = nil;
    
    CFRunLoopStop(self->_socketRunLoop);
    
    NSLog(@"RELASED SUCCESSFULLY!");
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    CFSocketContext ctx = {0, (__bridge void*)self, NULL, NULL, NULL};
    self->_serverSocket = CFSocketCreate(kCFAllocatorDefault,
                                        PF_INET,
                                        SOCK_STREAM,
                                        IPPROTO_TCP,
                                        kCFSocketAcceptCallBack, _handleConnect, &ctx);
    
    NSLog(@"Socket created %u", self->_serverSocket != NULL);
    
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_len = sizeof(sin);
    sin.sin_family = AF_INET;
    sin.sin_port = htons(30000);
    sin.sin_addr.s_addr= INADDR_ANY;
    
    CFDataRef sincfd = CFDataCreate(kCFAllocatorDefault,
                                    (UInt8 *)&sin,
                                    sizeof(sin));
    CFSocketSetAddress(self->_serverSocket, sincfd);
    CFRelease(sincfd);
    

    self->_source = CFSocketCreateRunLoopSource(kCFAllocatorDefault,
                                               self->_serverSocket,
                                               0);
    
    NSLog(@"Created source %u", self->_source != NULL);
    
    self->_socketRunLoop = CFRunLoopGetCurrent();
    CFRunLoopAddSource(self->_socketRunLoop,
                       self->_source,
                       kCFRunLoopCommonModes);
    
    NSLog(@"Registered into run loop");
    NSLog(@"Socket is %s", CFSocketIsValid(self->_serverSocket) ? "valid" : "invalid");
    NSLog(@"Source is %s", CFRunLoopSourceIsValid(self->_source) ? "valid" : "invalid");
}

@end

本格的なアプリはhttps://github.com/dpereira/confluxにあります。

ソケット (および関連リソース) のセットアップ/ティアダウンに何か問題がありますか?

4

1 に答える 1

0

ここでの問題は、リッスンしているソケットが TIME_WAIT になり、その状態で再びバインドできなかったことです。

CFSocket API によってエラーが返されなくても、POSIX ソケットの使用時に同じ状況が発生すると、ソケットの再バインド中にエラーが発生します。

解決策は、リッスンするためにソケットを再バインドする直前に、ソケットに SO_REUSEADDR オプションを設定するだけでした。

于 2015-06-14T22:18:01.240 に答える