Как я могу поймать EPIPE в моей обработке NSFIleHandle?

У меня проблема с EPIPE в моем приложении для iOS, и она не попадает в блок @try/@catch/@finally. Как я могу поймать этот сигнал (SIGPIPE, скорее всего)...

Я встроил "веб-прокси" в свое приложение, которое будет обрабатывать определенные типы URL-адресов - в этом случае ошибки кажется, что удаленный конец (также в моем приложении, но скрывающийся в библиотеках iOS) закрывает свой конец сокета, Я не получаю уведомления (я должен? Есть ли что-то, что я должен зарегистрировать с NSFileHandle, который мог бы помочь здесь?).

Я основал этот прокси на HTTPServer, который собрал Мэтт Галлахер (доступно здесь), и проблема в подклассе HTTPRequestHandler класс он собрал. Вот код (этот код является эквивалентом startResponse метод в базовом классе):

-(void)proxyTS:(SSProxyTSResource *)proxyTS didReceiveResource:(NSData *)resource
{
    NSLog(@"[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));

CFHTTPMessageRef response =
    CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);
    CFHTTPMessageSetHeaderFieldValue(response, 
                                    (CFStringRef)@"Content-Type", 
                                    (__bridge CFStringRef)s_MIMEtype);
    CFHTTPMessageSetHeaderFieldValue(response,
                                    (CFStringRef)@"Connection",
                                    (CFStringRef)@"close");
    CFHTTPMessageSetBody(response,
                        (__bridge CFDataRef)resource);

    CFDataRef headerData = CFHTTPMessageCopySerializedMessage(response);
    @try
    {
        NSLog(@" -> writing %u bytes to filehandle...",[((__bridge NSData *)headerData) length]);
        [self.fileHandle writeData:(__bridge NSData *)headerData];
    }
    @catch (NSException *exception)
    {
        // Ignore the exception, it normally just means the client
        // closed the connection from the other end.
    }
    @finally
    {
        NSLog(@" *ding*");
        CFRelease(headerData);
        CFRelease(response);
        [self.server closeHandler:self];
    }
}

И вот что отображается в журнале консоли при сбое:

Jan 15 14:55:10 AWT-NoTouch-iPhone-1 Streamer[1788] <Warning>: [SSProxyTSResponseHandler proxyTS:didReceiveResource:]
Jan 15 14:55:10 iPhone-1 Streamer[1788] <Warning>:  -> writing 261760 bytes to filehandle...
Jan 15 14:55:11 iPhone-1 com.apple.launchd[1] (UIKitApplication:com.XXX.Streamer[0xf58][1788]) <Warning>: (UIKitApplication:com.XXX.Streamer[0xf58]) Exited abnormally: Broken pipe: 13

Кажется, потому что другой конец закрыл трубу write() не удается, поэтому, если кто-то может указать мне, как я могу обнаружить, что он уже закрыт, и не пытаться записать в него данные, ИЛИ что бы это ни делало, это не приведет к сбою моей программы, что было бы очень полезно.

1 ответ

Непосредственная проблема сбоев с SIGPIPE решена. Я не совсем хихикаю насчет этого решения, но, по крайней мере, приложение не падает. Не ясно, что он работает на 100% правильно, но, похоже, ведет себя немного лучше.

Я решил эту проблему, изучив дальше, что происходит. Проводя некоторые исследования, я обнаружил, что, возможно, мне следует использовать NSFileHandle writeabilityHandler свойство установить блок, чтобы сделать запись. Я не полностью продан на этом подходе (мне это показалось запутанным), но это может помочь.

Решение для возможности написания:

При выполнении некоторых веб-поиска на writeabilityHandlerЯ наткнулся на запись в блоге Берта Люнга по некоторым вопросам, которые у него были в аналогичной области. Я взял его код и изменил его следующим образом, заменив @try/@catch/@finally блок выше с этим кодом:

self.pendingData = [NSMutableData dataWithData:(__bridge NSData *)(headerData)];

CFRelease(headerData);
CFRelease(response);

self.fileHandle.writeabilityHandler = ^(NSFileHandle* thisFileHandle)
    {
        int amountSent = send([thisFileHandle fileDescriptor],
                              [self.pendingData bytes],
                              [self.pendingData length],
                              MSG_DONTWAIT);
        if (amountSent < 0) {
            // errno is provided by system
            NSLog(@"[%@ %@] Error while sending response: %d", NSStringFromClass([self class]), NSStringFromSelector(_cmd), errno);
            // Setting the length to 0 will cause this handler to complete processing.
            self.pendingData.length = 0;
        } else {
            [self.pendingData replaceBytesInRange:NSMakeRange(0, amountSent)
                                   withBytes:NULL
                                      length:0];
        }

        if ([self.pendingData length] == 0) {
            thisFileHandle.writeabilityHandler = nil;
            // Hack to avoid ARC cycle with self. I don't like this, but...
            [[NSNotificationCenter defaultCenter] postNotification:self.myNotification];
        }
    };

Это работало нормально, но не решало проблему. Я все еще получал SIGPIPE/EPIPE.

SIGPIPE уйти!

Это не было сюрпризом, так как это делает то же самое, что и предыдущий writeData: сделал, но делает это с помощью send() вместо. Главное отличие в том, что использование send() позволяет errno установить. На самом деле это было очень полезно - я получал пару кодов ошибок (по ошибке), таких как 54 (Сброс соединения по пиру) и 32 (Сломанный канал). 54 были в порядке, но 32 привели к SIGPIPE/EPIPE. Затем до меня дошло - возможно, я должен просто игнорировать SIGPIPE.

Учитывая эту мысль, я добавил пару крючков в свой UIApplicationDelegate в application:didFinishLaunchingWithOptions:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    [self installSignalHandlers];

    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {

    ...

а также applicationWillTerminate::

- (void)applicationWillTerminate:(UIApplication *)application
{
    // Saves changes in the application's managed object context before the application terminates.

    [self removeSignalHandlers];

    [self saveContext];
}

-(void)installSignalHandlers
{
    signal(SIGPIPE,SIG_IGN);
}

-(void)removeSignalHandlers
{
    signal(SIGPIPE, SIG_DFL);
}

Теперь, по крайней мере, приложение не падает. Не ясно, что он работает на 100% правильно, но похоже, что он ведет себя.

Я также вернулся к @try/@catch/@finally структура, потому что это более прямо. Далее, после игнорирования SIGPIPE, @catch блок действительно срабатывает. Прямо сейчас я регистрирую исключение, но только так, чтобы я мог видеть, что это работает. В выпущенном коде этот журнал будет отключен.

Другие вопросы по тегам