1

Main problem

I'm implementing bandwidth management for Socket Rocket. To reduce amount of alteration in Socket Rocket I've decided to create subclass of NSOutputStream which will wrap NSOutputStream created for a socket. Concept is quite nice and should work like charm.

Encounter problem

In - (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len method I'm queering from bandwidth manager if data can be send or not. If not bandwidth manager gives me required delay for next write operation.

So my code looks more or less like this:

- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len
{
    auto bandwithInfo = [_bandwidthManager bandwidthInfoForSize: len];
    if (bandwithInfo.m_bOKtoSendNow)
    {
        auto actualWritten = [self.throttledStream write: buffer maxLength: len];
        if (actualWritten<0)
        {
            // report failure of sending data
            [_bandwidthManager reportNetworkBackPressure: len];
        }
        else
        {
            [_bandwidthManager ReportConsumedSendBandwidth: actualWritten];
            int bytesNotSentCount = len - actualWritten;
            if (bytesNotSentCount>0)
            {
                [_bandwidthManager ReportNetworkBackPressure: bytesNotSentCount];
            }
            [self enqueueEvent: NSStreamEventHasSpaceAvailable];
        }
        return actualWritten;
    }
    else
    {
        auto delay = bandwithInfo.m_nWaitTimeMilliseconds;
        NSASSERT(delay>0);
        [self scheduleNotifyReadyToWriteAfterMs: delay];
        return 0;
    }
}

- (void)scheduleNotifyReadyToWriteAfterMs: (int)miliseconds
{
    if (!isReadyToWriteScheduled)
    {
        isReadyToWriteScheduled = YES;
        static const NSTimeInterval kTimeIntervalMilisecond = 0.001;
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: miliseconds * kTimeIntervalMilisecond
                                                          target: self
                                                        selector: @selector(notifyReadyToWrite:)
                                                        userInfo: nil
                                                         repeats: false];
        NSLog(@"timer=%@\nRunLoop=%@", timer, [NSRunLoop currentRunLoop]);
// this alternative also doesn't work:
//      [self performSelector: @selector(notifyReadyToWrite:)
//                 withObject: nil
//                 afterDelay: miliseconds * kTimeIntervalMilisecond];
    }
}

- (void)notifyReadyToWrite: (NSTimer *)timer
{
    [timer invalidate];

    if (self.hasSpaceAvailable) {
        isReadyToWriteScheduled = NO;
        [self enqueueEvent: NSStreamEventHasSpaceAvailable];
    }
}

In logs I can see, (there is a run loop and it contains created timer):

timer=<__NSCFTimer: 0x109a96180>
RunLoop=<CFRunLoop 0x109a962d0 [0x7fff7208f440]>{wakeup port = 0x4507, stopped = false, ignoreWakeUps = true, 
current mode = (none),
common modes = <CFBasicHash 0x109a96390 [0x7fff7208f440]>{type = mutable set, count = 1,
entries =>
    2 : <CFString 0x7fff71fa1940 [0x7fff7208f440]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = (null),
modes = <CFBasicHash 0x109a963d0 [0x7fff7208f440]>{type = mutable set, count = 1,
entries =>
    2 : <CFRunLoopMode 0x109a96410 [0x7fff7208f440]>{name = kCFRunLoopDefaultMode, port set = 0x440b, queue = 0x109a964e0, source = 0x109a96570 (not fired), timer port = 0x4b03, 
    sources0 = (null),
    sources1 = (null),
    observers = (null),
    timers = <CFArray 0x109a95b10 [0x7fff7208f440]>{type = mutable-small, count = 1, values = (
    0 : <CFRunLoopTimer 0x109a96180 [0x7fff7208f440]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 485078914 (-0.00818103552 @ 3808422033916), callout = (NSTimer) [SRSendStreamWithThrottling notifyReadyToWrite:] (0x7fff87025d0d / 0x10264b100) (/Users/maru/Library/Developer/Xcode/DerivedData/AvayaCommonWorkspace-cnweundajqjciphewynfexnutumh/Build/Products/Debug/testrunner), context = <CFRunLoopTimer context 0x109a93370>}
)},
    currently 485078914 (3808417241824) / soft deadline in: 0.00479207 sec (@ 3808422033916) / hard deadline in: 0.004791892 sec (@ 3808422033916)
},

}
}

Now I have a run loop my custom stream and wrapped stream are scheduled in this run loop, so socket rocket and my wrapping class are reciveing all notifications, so I've rule out all obvious mistakes.

Still for some mysterious reason notifyReadyToWrite: is never called. Does anyone have a clue why?

4

1 回答 1

1

好的,我找到了问题的根源。

当 Socket Rocket 收到空间可用的通知时,它不会立即处理此通知,而是将通知分派到工作队列并在那里进行处理。
因此,当- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len调用方法时,当前事件循环与调度队列相关联,并且该队列功能不完整。您不能将计时器添加到此类运行循环。因此,将计时器添加到当前事件循环中,我将计时器添加到与流关联的事件循环中,并且计时器开始工作:

- (void)scheduleNotifyReadyToWriteAfterMs: (clientsdk::MillisecondTime)miliseconds
{
    if (!isReadyToWriteScheduled)
    {
        isReadyToWriteScheduled = YES;
        static const NSTimeInterval kTimeIntervalMilisecond = 0.001;

        NSTimer *timer = [NSTimer timerWithTimeInterval: miliseconds * kTimeIntervalMilisecond
                                                 target: self
                                               selector: @selector(notifyReadyToWrite:)
                                               userInfo: nil
                                                repeats: NO];

        [self enumerateRunLoopsUsingBlock:^(CFRunLoopRef runLoop) {
            CFRunLoopAddTimer(runLoop, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
        }];
    }
}

这解决了 NSTimer 的问题。

于 2016-05-18T21:56:55.707 回答