4

根据 Apple 的“轮询与运行循环调度”:

[ hasSpace/BytesAvailable] 可能意味着有可用的字节或空间,或者找出的唯一方法是尝试读取或写入操作(这可能导致暂时阻塞)。

该文档没有明确说明 hasSpace/BytesAvailable事件的行为方式相同,只是模糊地表明它们具有“相同的语义”。

我是否可以得出结论,写入/读取流错误或字节读取/写入返回小于预期量可能是由于“瞬时阻塞”?

如果是这样,我应该再次尝试传输吗?我应该使用某种计时器机制让堵塞有机会清除吗?这将是很多工作要实施,所以如果它不太可能有帮助,我宁愿不这样做。

(在这种情况下启动有限轮询循环是很诱人的,比如说一个进行 10 次尝试的 while 循环,但我不知道在运行循环中调度流的同时这样做是否安全,并且我没有办法测试它。)

4

3 回答 3

1

这是一个漫长的过程。以下是对此问题的一些后续行动:

早些时候,我放弃了维护和检查剩余缓存的想法,因为这仅适用于输出流,而进一步的反射表明输入流也可能被阻塞。

相反,我设置了空闲的while循环:

- (void) stream:(NSStream *)theStream handleEvent:(NSStreamEvent)eventCode {

    switch (eventCode) 
        // RECEIVING
    case NSStreamEventHasBytesAvailable: {
        if (self.receiveStage == kNothingToReceive)
            return;
        // Get the data from the stream. (This method returns NO if bytesRead < 1.)
        if (![self receiveDataViaStream:(NSInputStream *)theStream]) {
            // If nothing was actually read, consider the stream to be idling.
            self.bStreamIn_isIdling = YES;
            // Repeatedly retry read, until (1) the read is successful, or (2) stopNetwork is called, which will clear the idler.
            // (Just in case, add nil stream property as a loop breaker.) 
            while (self.bStreamIn_isIdling && self.streamIn) {
                if ([self receiveDataViaStream:(NSInputStream *)theStream]) {
                    self.bStreamIn_isIdling = NO;
                    // The stream will have started up again; prepare for next event call.
                    [self assessTransmissionStage_uponReadSuccess];
                }
            }
        }
        else
            // Prepare for what happens next.
            [self assessTransmissionStage_uponReadSuccess];
        break;
        // SENDING
    case NSStreamEventHasSpaceAvailable: 
        if (self.sendStage == kNothingToSend)
            return;
        if (![self sendDataViaStream:(NSOutputStream *)theStream]) {
            self.bStreamOut_isIdling = YES;
            while (self.bStreamOut_isIdling && self.streamOut) {
                if ([self sendDataViaStream:(NSOutputStream *)theStream]) {
                    self.bStreamOut_isIdling = NO;
                    [self assessTransmissionStage_uponWriteSuccess];
                }
            }
        }
        else
            [self assessTransmissionStage_uponWriteSuccess]; 
        break;
    // other event cases…

然后是时候通过“取消”按钮测试用户发起的取消了。在同步的中途,Cocoa 端有一个暂停,等待用户输入。如果用户此时取消,Cocoa 应用程序会关闭流并将它们从运行循环中删除,所以我预计连接另一端的流会生成NSStreamEventEndEncountered事件,或者可能是NSStreamEventErrorOccurred. 但是,不,只有一个事件发生了,一个NSStreamEventHasBytesAvailable!去搞清楚。

当然,实际上并没有任何“可用字节”,因为流已经在 Cocoa 端关闭,而不是写入——所以 iOS 端的流处理程序进入了无限循环。不太好。

接下来我测试了如果其中一个设备进入睡眠状态会发生什么。在用户输入暂停期间,我让 iPhone 通过 auto-lock* 进入睡眠状态,然后在 Cocoa 端提供用户输入。再次惊喜:Cocoa 应用程序一直没有受到干扰,直到同步结束,当我唤醒 iPhone 时,iOS 应用程序证明也完成了同步。

我的空闲循环修复了 iPhone 端的打嗝吗?我投入了一个停止网络例程来检查:

if (![self receiveDataViaStream:(NSInputStream *)theStream])
    [self stopNetwork]; // closes the streams, etc.

同步仍然运行到完成。没有打嗝。

最后,我测试了如果Mac(Cocoa 端)在输入暂停期间进入睡眠状态会发生什么。这产生了一种向后的打嗝:在MacNSStreamEventErrorOccurred端接收到两个事件,之后不再可能写入输出流。iPhone 端根本没有收到任何事件,但是如果我测试 iPhone 的流状态,它将返回 5,NSStreamStatusAtEnd。

结论与计划:

  • “临时区块”有点像独角兽。网络要么运行顺畅,要么完全断开连接。
  • 如果真的有临时阻塞这样的东西,那就没有办法将它与完全断开区分开来。对于临时块来说,唯一看起来合乎逻辑的流状态常量是NSStreamStatusAtEndNSStreamStatusError。但是根据上述实验,这些表明断开连接。
  • 结果,我丢弃了 while 循环,并且仅通过检查 bytesRead/Written < 1 来检测断开连接。

*如果 iPhone 受制于 Xcode,它将永远不会休眠。您必须直接从 iPhone 运行它。

于 2012-05-12T19:54:38.517 回答
1

这是一个很好的套接字包装器:https ://github.com/robbiehanson/CocoaAsyncSocket

如果连接不可用,它将对读取和写入进行排队。您没有提到您使用的是 UDP 还是 TCP,但是我怀疑您使用的是 TCP,在这种情况下,它会自行处理任何中断——前提是连接不会被断开。

于 2012-01-06T15:58:54.430 回答
1

每当您尝试将 0 字节写入输出流或在输入流上收到 0 字节时,您都可以预料到“断开连接”。如果要保持流活动,请确保检查要写入输出流的字节长度。这样,输入流永远不会收到 0 字节,这会触发关闭流的事件处理程序。

没有“空闲”输出流之类的东西。只是输出流的空闲字节提供者,不需要指示其空闲状态。

如果您通过睡眠计时器断开网络连接,您可以在打开流时禁用它,然后在关闭它们时禁用它:

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
    switch(eventCode) {
        case NSStreamEventOpenCompleted:
        {
            [UIApplication sharedApplication].idleTimerDisabled = YES;
            break;
        }

        case NSStreamEventEndEncountered:
        {
            [UIApplication sharedApplication].idleTimerDisabled = NO;
            break;
        }
    }
}

我不会进一步深入研究您的具体情况,因为我可以立即告诉您,您并不完全清楚流是什么。我知道有关流的文档在启动新手方面确实很差,而且很少启动;但是,它们对已经存在 30 年的相同流进行建模,因此任何操作系统(Windows 除外)的流上的任何文档都可以完美地帮助您加快速度。

顺便说一句,流的另一个不可分割的部分是您的网络连接代码,您没有提供。我建议,如果您还没有使用 NSNetService 和 NSNetServiceBrowser 来查找对等点、连接到它们并相应地获取您的流,那么您应该这样做。这样做可以让您轻松监控网络连接的状态,并在流意外关闭时快速轻松地重新打开流。

我为此提供了非常详尽但易于遵循的示例代码,如果有人愿意,您根本不需要自定义即可使用。

于 2018-02-26T14:21:48.997 回答