Как я могу поймать 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
блок действительно срабатывает. Прямо сейчас я регистрирую исключение, но только так, чтобы я мог видеть, что это работает. В выпущенном коде этот журнал будет отключен.