2

ObjectiveC/iOS に単純な TCP サーバーを実装しようとしています。最小限の例は次のようになります。 サーバーは複数の TCP 接続を維持する必要があります。データ (4 バイト) が読み取られるたびに、サーバーはその接続で応答 (4 バイト) を返します。

最初にソケット リスナーを作成し、それを CFRunLoop でスケジュールします。

CFSocketRef initialCfSock = CFSocketCreate(                                            
                                          kCFAllocatorDefault,
                                          PF_INET,
                                          SOCK_STREAM,
                                          IPPROTO_TCP,
                                          kCFSocketAcceptCallBack,
                                          &handleConnect,
                                          NULL);
if( ! initialCfSock ) return;
int yes=1;
setsockopt(CFSocketGetNative(initialCfSock), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));

struct sockaddr_in sin = {0};
sin.sin_len = sizeof(sin);
sin.sin_family = AF_INET;
sin.sin_port = htons(50000);
sin.sin_addr.s_addr= htonl(INADDR_ANY);
CFDataRef sincfd = CFDataCreate(
                                kCFAllocatorDefault,
                                (UInt8 *)&sin,
                                sizeof(sin));
if (kCFSocketSuccess != CFSocketSetAddress(initialCfSock, sincfd)) return;
CFRelease(sincfd);

CFRunLoopSourceRef socketsource = CFSocketCreateRunLoopSource(
                                                              kCFAllocatorDefault,
                                                              initialCfSock,
                                                              0);
CFRunLoopAddSource(
                   CFRunLoopGetCurrent(),
                   socketsource,
                   kCFRunLoopCommonModes);
CFRelease(socketsource);
CFRunLoopRun();

次に、着信接続で、接続ハンドラーが呼び出されます (これは機能します)。

NSInputStream * inputStream = NULL; // global variable
NSOutputStream * inputStream = NULL; // global variable
void handleConnect (CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info)
{
    NSLog(@"handleConnect");
    CFSocketNativeHandle socketHandle = *(CFSocketNativeHandle *) data;

    CFReadStreamRef currentInputStream = NULL;
    CFWriteStreamRef currentOutputStream = NULL;

    CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketHandle, (CFReadStreamRef *) &currentInputStream, (CFWriteStreamRef *) &currentOutputStream);
    if (! currentInputStream || ! currentOutputStream)
        return;

    // runloop registration
    CFStreamClientContext myContext = {
        0,
        NULL,
        NULL,
        NULL
    };
    CFOptionFlags registeredEventsInput = kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
    if (! CFReadStreamSetClient(currentInputStream, registeredEventsInput, handleInput, &myContext))
        return;
    CFReadStreamScheduleWithRunLoop(currentInputStream, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    CFOptionFlags registeredEventsOutput = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
    if (! CFWriteStreamSetClient(currentOutputStream, registeredEventsOutput, handleOutput, &myContext))
        return;
    CFWriteStreamScheduleWithRunLoop(currentOutputStream, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    if (!CFReadStreamOpen(currentInputStream))
        return;
    if (!CFWriteStreamOpen(currentOutputStream))
        return;

    inputStream = (NSInputStream *)currentInputStream;
    outputStream = (NSOutputStream *)currentOutputStream;

    NSLog(@"handleConnect: inputStream=%p, outputStream=%p", currentInputStream, currentOutputStream);

    // 2)

}

最後に、着信バイトで、入力ハンドラーが呼び出されます。4 バイトが読み取られ、4 バイトの応答が送信されます。

void handleInput(CFReadStreamRef currentInputStream, CFStreamEventType event, void *clientCallbackInfo)
{
    NSLog(@"handleInput: inputStream=%p, outputStream=%p", inputStream, outputStream);

    switch(event) {
        case kCFStreamEventHasBytesAvailable:
        {
            // 1) begin
            unsigned int a;
            NSLog(@"before read: %d/%d, spaceAvail %d, bytesAvail %d", [inputStream streamStatus], [outputStream streamStatus], [outputStream hasSpaceAvailable], [inputStream hasBytesAvailable]);
            int err = [inputStream read:(uint8_t *)&a maxLength:sizeof(a)];
            NSLog(@"after read: %d/%d, spaceAvail %d, bytesAvail %d, err=%d", [inputStream streamStatus], [outputStream streamStatus], [outputStream hasSpaceAvailable], [inputStream hasBytesAvailable], err);

            NSLog(@"before write: %d/%d, spaceAvail %d, bytesAvail %d", [inputStream streamStatus], [outputStream streamStatus], [outputStream hasSpaceAvailable], [inputStream hasBytesAvailable]);
            err = [outputStream write:(uint8_t *) &a maxLength:sizeof(a)];
            NSLog(@"after write: %d/%d, spaceAvail %d, bytesAvail %d, err=%d", [inputStream streamStatus], [outputStream streamStatus], [outputStream hasSpaceAvailable], [inputStream hasBytesAvailable], err);
            // 1) end
            break;
        }
        case kCFStreamEventErrorOccurred:
        case kCFStreamEventEndEncountered:
        default:
            Log(@"[PK] handleInput: stream end/error");
            break;
    }

}

問題は、handleInput 中の [outputStream write:...] 呼び出しがバイトを送信せず、代わりにブロックされて何も起こらないことです。クライアントは同時にデータを待ちます。

これはログです:

handleConnect
handleConnect: inputStream=0x15576cb0, outputStream=0x15576ce0
before read: 2/2, spaceAvail 0, bytesAvail 1
after read: 2/2, spaceAvail 0, bytesAvail 1, err=4
before write: 2/2, spaceAvail 0, bytesAvail 0

しかし: 1)から2)でマークされたhandleInputの行を移動すると、すべて正常に動作します。しかし、私はデータを同期的に待ちますが、これはしたくありません。

次に、対応するログファイルは次のようになります。

handleConnect
handleConnect: inputStream=0x17632d30, outputStream=0x17632310
before read: 2/2, spaceAvail 0, bytesAvail 0
after read: 2/2, spaceAvail 1, bytesAvail 1, err=4
before write: 2/2, spaceAvail 1, bytesAvail 0
after write: 2/2, spaceAvail 1, bytesAvail 0, err=4

ここで何が問題なのですか?

4

0 に答える 0