5

iPhoneとMacの間にBonjourネットワークを設定しました。

ユーザーは、Macに表示されるテーブルでiPhoneのネットサービスを選択すると、ストリームのペアが作成され、両側で開かれます。

iPhoneは、コード(整数)をMacに送信することから始めます。Macはそれを正常に受信します。

ユーザー入力と処理を一時停止した後、MacはiPhoneへのコードの送信を開始します。

NSInteger bytesWritten = [self.streamOut write:buffer maxLength:sizeof(uint8_t)];
// bytesWritten is 1.

ただし、iPhoneがNSStreamEventHasBytesAvailableイベントを取得することはありません。この時点の直前に再確認したところ、iPhoneのNSInputStreamのstreamStatusは2であり、これはNSStreamStatusOpenです。

何が間違っている可能性があるのか​​?


更新:Macが最初にiPhoneに整数を送信するテストを実行しました。繰り返しになりますが、Macの出力ストリームからbytesWritten of 1を取得しましたが、iPhoneはNSStreamEventHasBytesAvailableイベントを取得しませんでした。

したがって、iPhoneの入力ストリームに何か問題があるはずです。しかし、私は再確認しました:

  • iPhoneのself.streamInは、hファイルにNSInputStreamとして正しく入力されています
  • iPhoneは2つのNSStreamEventOpenCompletedイベントを受け取り、ストリーム引数のクラスを確認します。1つはKindOfClass:[NSOutputStreamクラス]で、もう1つはそうではありません。
  • iPhoneは、NSStreamEventEndEncountered、NSStreamEventErrorOccurred、またはNSStreamEventNoneを受信しません。
  • 上記のように、Macの出力ストリームへの書き込みに続いて、iPhoneの入力ストリームステータスは2、NSStreamStatusOpenです。

iPhoneの入力ストリームを作成するために使用されるコードは次のとおりです。Cスタイルのソケットコールバック関数で行われるため、CFタイプを使用します。

CFReadStreamRef readStream = NULL;
CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, &readStream, NULL);
if (readStream) {
    CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
    server.streamIn = (NSInputStream *)readStream;
    server.streamIn.delegate = server;
    [server.streamIn scheduleInRunLoop:[NSRunLoop currentRunLoop] 
                               forMode:NSDefaultRunLoopMode];
    if ([server.streamIn streamStatus] == NSStreamStatusNotOpen)
        [server.streamIn open];
    CFRelease(readStream);
}

Update2:alastairのコメントに対応する情報:

ソケットオプション

保持、解放、およびcopyDescriptionコールバックはNULLに設定されます。optionFlagsはacceptCallbackに設定されています。

ソケットの作成

これがiPhoneとMacの両方でソケットをセットアップするために使用される方法であり、さまざまなチュートリアルと実験(うまくいった)から適応されたこのコードで実際に何が起こっているのかを理解するための私のコメントされた試みで完了します:

/**
 Socket creation, port assignment, socket scheduled in run loop.
 The socket represents the port on this app's end of the connection.
 */
- (BOOL) makeSocket {
    // Make a socket context, with which to configure the socket.
    // It's a struct, but doesn't require "struct" prefix -- because typedef'd?
CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL}; // 2nd arg is pointer for callback function   
    // Make socket.
    // Sock stream goes with TCP protocol, the safe method used for most data transmissions.
    // kCFSocketAcceptCallBack accepts connections automatically and presents them to the callback function supplied in this class ("acceptSocketCallback").
    // CFSocketCallBack, the callback function itself.
    // And note that the socket context is passed in at the end.
    self.socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&acceptSocketCallback, &socketCtxt);

    // Do socket-creation error checking.
    if (self.socket == NULL) {
        // alert omitted
        return NO;
    }

    // Prepare an int to pass to setsockopt function, telling it whether to use the option specified in arg 3.
    int iSocketOption = 1; // 1 means, yes, use the option

    // Set socket options.
    // arg 1 is an int. C-style method returns native socket.
    // arg 2, int for "level." SOL_SOCKET is standard.
    // arg 3, int for "option name," which is "uninterpreted." SO_REUSEADDR enables local address reuse. This allows a new connection even when a port is in wait state.
    // arg 4, void (wildcard type) pointer to iSocketOption, which has been set to 1, meaning, yes, use the SO_REUSEADDR option specified in arg 3.
    // args 5, the size of iSocketOption, which can now be recycled as a buffer to report "the size of the value returned," whatever that is. 
    setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&iSocketOption, sizeof(iSocketOption));

    // Set up a struct to take the port assignment.
    // The identifier "addr4" is an allusion to IP version 4, the older protocol with fewer addresses, which is fine for a LAN.
    struct sockaddr_in addr4;
    memset(&addr4, 0, sizeof(addr4));
    addr4.sin_len = sizeof(addr4);
    addr4.sin_family = AF_INET;
    addr4.sin_port = 0; // this is where the socket will assign the port number
    addr4.sin_addr.s_addr = htonl(INADDR_ANY);
    // Convert to NSData so struct can be sent to CFSocketSetAddress.
    NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)];

    // Set the port number.
    // Struct still needs more processing. CFDataRef is a pointer to CFData, which is toll-free-bridged to NSData.
    if (CFSocketSetAddress(socket, (CFDataRef)address4) != kCFSocketSuccess) {
        // If unsuccessful, advise user of error (omitted)…
        // ... and discard the useless socket.
        if (self.socket) 
            CFRelease(socket);
        self.socket = NULL;
        return NO;
    }

    // The socket now has the port address. Extract it.
    NSData *addr = [(NSData *)CFSocketCopyAddress(socket) autorelease];
    // Assign the extracted port address to the original struct.
    memcpy(&addr4, [addr bytes], [addr length]);
    // Use "network to host short" to convert port number to host computer's endian order, in case network's is reversed.
    self.port = ntohs(addr4.sin_port);
    printf("\nUpon makeSocket, the port is %d.", self.port);// !!!:testing - always prints a 5-digit number

    // Get reference to main run loop.
    CFRunLoopRef cfrl = CFRunLoopGetCurrent();
    // Schedule socket with run loop, by roundabout means.
    CFRunLoopSourceRef source4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
    CFRunLoopAddSource(cfrl, source4, kCFRunLoopCommonModes);
    CFRelease(source4);

    // Socket made
    return YES;
}

ランループスケジューリング

はい、4つのストリームすべてがrunloopでスケジュールされ、すべて上記の最初の更新で投稿したものと同等のコードを使用しています。

ランループブロッキング:

同期、複数のスレッド、NSLocksなどで特別なことは何もしていません。また、コンソールに何かを印刷するようにボタンアクションを設定すると、それは全体で機能します—runloopは正常に実行されているようです。


Update4、ストリームポート?

Noaのデバッグの提案は、ストリームのプロパティをさらに調べるというアイデアを私に与えました。

NSNumber *nTest = [self.streamIn propertyForKey:NSStreamSOCKSProxyPortKey]; // always null!

ストリームがポートにぶら下がっていると思っていましたが、驚くべきことに、nTest常にnullです。私のアプリではnullであり、問​​題を示しているように見えますが、動作するチュートリアルアプリでもnullです。ストリームが作成された後、そのポート割り当てにハングアップする必要がない場合、ポートプロパティの目的は何ですか?

たぶん、ポートプロパティに直接アクセスできませんか?ただしnTest、次の場合も常にnullです。

    NSDictionary *dTest = [theInStream propertyForKey:NSStreamSOCKSProxyConfigurationKey];
    NSNumber *nTest = [dTest valueForKey:NSStreamSOCKSProxyPortKey];
    NSLog(@"\tInstream port is %@.", nTest); // (null)
    nTest = [dTest valueForKey:NSStreamSOCKSProxyPortKey];
    NSLog(@"\tOutstream port is %@.", nTest); // (null)
4

1 に答える 1

2

問題はこの行でした:

CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, &readStream, NULL);

iPhone側でのみデータを受信して​​いれば、これは問題ありませんでした。しかし、入力ストリームだけでなく、ストリームのペアを作成していたので、このコードの下に書き込みストリームを作成していました。

CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, NULL, &writeStream); 

CFStreamリファレンスには、「[readStreamに] NULLを渡すと、この関数は読み取り可能なストリームを作成しません」と記載されています。NULLを渡すと、以前に作成したストリームが動作不能になるとは言えません。しかし、それは明らかに何が起こるかです。

この設定の奇妙なアーティファクトの1つは、最初にstreamInを開いた場合、逆の問題が発生することでした。iPhoneはhasByteAvailableイベントを取得しますが、hasSpaceAvailableイベントは取得しません。また、質問で述べたように、ストリームのステータスを照会すると、両方ともNSStreamStatusOpenを返します。そのため、本当の間違いがどこにあるのかを理解するのに長い時間がかかりました。

(このシーケンシャルストリームの作成は、数か月前に設定したテストプロジェクトの成果物であり、データが一方向または他の方向にのみ移動することをテストしました。)

解決

両方のストリームは、1行でペアとして作成する必要があります。

CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, &readStream, &writeStream);
于 2012-05-06T16:58:32.013 に答える