bytesWritten,但其他设备从不接收NSStreamEventHasBytesAvailable事件

时间:2012-04-30 02:58:18

标签: ios cocoa bonjour nsstream

我在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的输出流中获得了1的bytesWritten,但iPhone从未得到过NSStreamEventHasBytesAvailable事件。

所以iPhone的输入流一定有问题。但我仔细检查了一下:

  • iPhone的self.streamIn在h文件中正确输入为NSInputStream
  • iPhone收到2个NSStreamEventOpenCompleted事件,我检查了流arg的类。一个是KindOfClass:[NSOutputStream类],另一个不是。
  • iPhone永远不会收到NSStreamEventEndEncountered,NSStreamEventErrorOccurred或NSStreamEventNone。
  • 如上所述,在Mac写入输出流后,iPhone的输入流状态为2,NSStreamStatusOpen。​​

以下是用于创建iPhone输入流的代码。它使用CF类型,因为它是在C风格的套接字回调函数中完成的:

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的评论:

套接字选项

retain,release和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;
}

Runloop Scheduling

是的,所有4个流都在runloop中安排,所有使用的代码都相当于我在上面第一次更新中发布的代码。

Runloop阻止:

我没有做任何与同步,多线程,NSLocks等有关的事情。如果我设置一个按钮动作来打印控制台的东西,它可以在整个过程中运行 - runloop似乎正常运行。


Update4,Stream Ports?

Noa的调试建议让我有了进一步检查流属性的想法:

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

我曾假设流被挂在他们的端口上,但令人惊讶的是,nTest始终为空。它在我的应用程序中为空,这似乎指向一个问题 - 但它在工作的教程应用程序中也为空。如果流创建后不需要挂起其端口分配,那么端口属性的目的是什么?

也许端口属性不能直接访问?但是nTest在下面也总是为空:

    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)

1 个答案:

答案 0 :(得分:2)

问题在于这一行:

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

如果我只在iPhone端接收数据,那就没问题。但我正在创建一个的流,而不仅仅是一个输入流,所以在这段代码下我创建了一个写流:

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

CFStream Reference说,“如果你传递NULL [for readStream],这个函数将不会创建一个可读的流。”它没有说,如果你传递NULL,你将渲染以前创建的流不可操作。但这显然发生了什么。

这个设置的一个奇怪的工件是,如果我首先打开streamIn,我会遇到相反的问题:iPhone将获得hasByteAvailable事件,但从未发生过hasSpaceAvailable事件。正如问题所述,如果我查询了流的状态,两者都会返回NSStreamStatusOpen。所以花了很长时间才弄清楚真正的错误在哪里。

(这个顺序流创建是我几个月前设置的测试项目的工件,我测试的数据只在一个方向或另一个方向上移动。)

<强>解

两个流应该在一行中成对创建:

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