1

我在我的 iOS 应用程序中遇到了 EPIPE 问题,并且它没有被 @try/@catch/@finally 块捕获。我怎样才能捕捉到这个信号(SIGPIPE,可能)......

我在我的应用程序中构建了一个“网络代理”,它将处理某些类型的 URL - 在这种错误情况下,似乎远程端(也在我的应用程序中,但隐藏在 iOS 库中)关闭了它的套接字端. 我没有收到通知(我应该吗?有什么我应该在 NSFileHandle 注册的东西可能对这里有帮助吗?)。

我已经将此代理基于 Matt Gallagher 放在一起的 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()失败了,所以如果有人能指出我如何发现它已经关闭而不尝试向它写入数据,或者任何使它不会崩溃的程序,那将是非常有帮助。

4

1 回答 1

3

SIGPIPE 崩溃的直接问题得到了解决。我对这个解决方案并不完全傻笑,但至少应用程序不会崩溃。目前尚不清楚它是否 100% 正确工作,但它的表现似乎要好一些。

我通过进一步检查正在发生的事情解决了这个问题。在做一些研究时,我发现也许我应该使用 NSFileHandle 的writeabilityHandler属性来安装一个块来进行写作。我并没有完全接受这种方法(这让我感到很困惑),但它可能会有所帮助。

可写处理程序解决方案:

在进行一些网络搜索时writeabilityHandler,我偶然发现了Bert Leung 的博客文章,其中提到了他在类似领域遇到的一些问题。我拿了他的代码并修改如下,@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()。关键区别在于 usingsend()允许errno设置。实际上,这很有帮助 - 我收到了几个错误代码(在 errno 中),例如 54(对等连接重置)和 32(断管)。54 的很好,但 32 的结果是 SIGPIPE/EPIPE。然后我恍然大悟——也许我应该忽略 SIGPIPE。

考虑到这个想法,我在我UIApplicationDelegate的 in 中添加了几个钩子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块确实会被触发。现在,我正在记录异常,但只有这样我才能看到它正在工作。在发布的代码中,该日志将被禁用。

于 2013-01-17T15:53:24.983 回答