我的iOS应用程序中存在EPIPE问题,并且它没有被@ try / @ catch / @ finally块捕获。我怎么能抓住这个信号(可能是SIGPIPE)......
我在我的应用程序中构建了一个“Web代理”,它将处理某些类型的URL - 在这个错误的情况下,似乎远程端(也在我的应用程序中,但隐藏在iOS库中)关闭了它的结束套接字。我没有得到通知(我应该吗?有什么东西我应该注册NSFileHandle可能有帮助吗?)。
我在Matt Gallagher放在一起的HTTPServer上建立了这个代理(可用here),问题出在他放在一起的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()
失败了,所以如果有人可以指出我怎么能发现它已经关闭而不是试图向它写入数据或者是什么使它成为它不会崩溃我的程序,这将是非常有帮助的。
答案 0 :(得分:3)
解决了使用SIGPIPE崩溃的直接问题。我对这个解决方案并不完全傻笑,但至少应用程序不会崩溃。目前尚不清楚它是否正常工作,但它似乎确实表现得更好。
我通过进一步检查发生了什么来解决这个问题。在做一些研究时,我发现也许我应该使用NSFileHandle的writeabilityHandler
属性来安装一个块来进行写作。我没有完全以这种方式出售(对我来说感觉很复杂),但它可能会有所帮助。
在对writeabilityHandler
进行网上搜索时,我偶然发现Bert Leung's blog entry他在类似区域遇到的一些问题。我拿了他的代码并按如下方式修改它,用以下代码替换上面的@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。
这并不奇怪,因为这与前writeData:
完全相同,但使用send()
代替。但关键的区别在于使用send()
可以设置errno
。实际上,这非常有用 - 我得到了几个错误代码(在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);
}
现在至少应用程序不会崩溃。目前尚不清楚它是否正常工作,但似乎确实表现得很好。
我也切换回@try/@catch/@finally
结构,因为它更直接。此外,在忽略SIGPIPE之后,@catch
块会被触发。现在,我正在记录异常,但只有这样我才能看到它正在运行。在已发布的代码中,该日志将被禁用。