1

iOS アプリの EPIPE に問題があり、@try/@catch/@finally ブロックでキャッチされません。どうすればこのシグナルをキャッチできますか (SIGPIPE の可能性が高い)...

特定の種類の URL を処理する「Web プロキシ」をアプリに組み込みました。このエラーの場合、リモート エンド (これもアプリ内ですが、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プロパティを使用して、書き込みを行うブロックをインストールする必要があることがわかりました。私はそのアプローチに完全に納得しているわけではありませんが (複雑に感じました)、役立つかもしれません。

書き込み可能ハンドラー ソリューション:

で Web 検索を行っているときに、 Bert Leungが同様の分野で抱えていたいくつかの問題についてのブログ エントリ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 (壊れたパイプ) などのいくつかのエラー コード (errno で) を取得していました。54 では問題ありませんでしたが、32 では SIGPIPE/EPIPE が発生しました。それからそれは私に夜明けをもたらしました - おそらく私は SIGPIPE を無視するべきです。

その考えを踏まえて、私は私の中にいくつかのフックを追加しましUIApplicationDelegateapplication: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 に答える