SIGPIPE'leri önleme (veya düzgün bir şekilde kullanma)


261

TCP veya yerel UNIX soketindeki bağlantıları kabul eden, basit bir komut okuyan ve komuta bağlı olarak bir yanıt gönderen küçük bir sunucu programım var. Sorun şu ki, istemcinin cevaba bazen ilgisi olmayabilir ve erken çıkar, bu nedenle bu sokete yazmak SIGPIPE'ye neden olur ve sunucumun çökmesine neden olur. Buradaki çarpışmayı önlemek için en iyi uygulama nedir? Çizginin diğer tarafının hala okunup okunmadığını kontrol etmenin bir yolu var mı? (select () burada her zaman soketin yazılabilir olduğunu söylediği için çalışmıyor). Yoksa SIGPIPE'i bir işleyiciyle yakalayıp görmezden gelmeli miyim?

Yanıtlar:


255

Genelde SIGPIPE hatayı ve doğrudan kodunuzda işlemek . Bunun nedeni, C'deki sinyal işleyicilerin yapabilecekleri konusunda birçok kısıtlamaya sahip olmasıdır.

Bunu yapmanın en taşınabilir yolu, SIGPIPEişleyiciyi ayarlamaktır SIG_IGN. Bu, herhangi bir soket veya boru yazma işlemininSIGPIPE sinyale .

SIGPIPESinyali yok saymak için aşağıdaki kodu kullanın:

signal(SIGPIPE, SIG_IGN);

send()Çağrıyı kullanıyorsanız , başka bir seçenek MSG_NOSIGNALde SIGPIPEdavranışı çağrı bazında kapatan seçeneği kullanmaktır . Tüm işletim sistemlerinin aşağıdakileri desteklemediğini unutmayın:MSG_NOSIGNAL bayrağı .

Son olarak, bazı işletim sistemlerinde SO_SIGNOPIPEayarlanabilen soket bayrağını da dikkate almak isteyebilirsiniz setsockopt(). Bu, SIGPIPEyalnızca ayarlandığı soketlere yazma işlemlerinin neden olmasını önleyecektir .


75
Bunu açıkça belirtmek için: sinyal (SIGPIPE, SIG_IGN);
jhclark

5
141 (128 + 13: SIGPIPE) çıkış dönüş kodu alıyorsanız bu gerekli olabilir. SIG_IGN yoksay sinyal işleyicisidir.
velcrow

6
Soketler için, @talash'ın belirttiği gibi, MSG_NOSIGNAL ile send () kullanmak gerçekten daha kolay.
Pawel Veselov

3
Programım kırık bir boruya (benim durumumda bir soket) yazarsa tam olarak ne olur? SIG_IGN, programı SIG_PIPE'yi yok sayar, ancak bu, send () 'in istenmeyen bir şey yapmasına neden olur mu?
sudo

2
Bir kez bulduğumdan, daha sonra unuttum ve karıştım, sonra ikinci kez keşfettim. Hata ayıklayıcılar (gdb'nin yaptığını biliyorum) sinyal işleminizi geçersiz kılar, bu nedenle yok sayılan sinyaller yok sayılmaz! SIGPIPE'nin artık oluşmadığından emin olmak için kodunuzu bir hata ayıklayıcı dışında test edin. stackoverflow.com/questions/6821469/…
Jetski S tipi

155

Başka bir yöntem soketi değiştirerek asla write () üzerinde SIGPIPE oluşturmaz. Bu, SIGPIPE için genel bir sinyal işleyici istemeyebileceğiniz kütüphanelerde daha uygundur.

Çoğu BSD tabanlı (MacOS, FreeBSD ...) sistemde (C / C ++ kullandığınızı varsayarsak), bunu aşağıdakilerle yapabilirsiniz:

int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));

Bu geçerliyken, SIGPIPE sinyali üretilmek yerine EPIPE döndürülecektir.


45
Bu kulağa hoş geliyor, ancak SO_NOSIGPIPE Linux'ta mevcut görünmüyor - bu nedenle geniş anlamda taşınabilir bir çözüm değil.
nobar

1
Herkese selam, bana bu kodu nereye koymak zorunda söyleyebilir misiniz? teşekkür
edemiyorum

9
Linux'ta, gönderme
Aadishri

Mac OS X'te mükemmel çalışıyor
kirugan

116

Partiye çok geç kaldım ama SO_NOSIGPIPE taşınabilir değil ve sisteminizde çalışmayabilir (BSD bir şey gibi görünüyor).

Diyelim ki, olmadan bir Linux sistemi kullanıyorsanız SO_NOSIGPIPE,MSG_NOSIGNAL gönder (2) çağrınızda bayrağı .

, Örnek değiştirilmesi write(...)ile send(...,MSG_NOSIGNAL)(bakınız nobar 'yorumunu)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );

5
Başka bir deyişle, write () yerine send (..., MSG_NOSIGNAL) kullanın ve SIGPIPE almazsınız. Bu, soketler için (desteklenen platformlarda) iyi çalışmalıdır, ancak send (), soketlerle (borular değil) kullanımla sınırlı görünmektedir, bu nedenle bu SIGPIPE sorununa genel bir çözüm değildir.
nobar

@ Ray2k Dünyada hala Linux <2.2 için uygulamalar geliştiren var mı? Bu aslında yarı ciddi bir soru; dağıtımların çoğu en az 2.6 ile gönderilir.
Part Atışı

1
@Parthian Shot: Cevabınızı yeniden düşünmelisiniz. Eski gömülü sistemlerin bakımı, eski Linux sürümlerini korumak için geçerli bir nedendir.
kirsche40

1
@ kirsche40 Ben OP değilim- Sadece merak ettim.
Part atış

@sklnd bu benim için mükemmel çalıştı. QTcpSocketYuva kullanımını sarmak için bir Qt nesnesi kullanıyorum , writebir OS send( socketDescriptoryöntem kullanarak ) tarafından yöntem çağrısı değiştirmek zorunda kaldı . Herkes bu seçeneği bir QTcpSocketsınıfta ayarlamak için temiz bir seçenek biliyor mu?
Emilio González Montaña

29

Bu ise yazı ne SO_NOSIGPIPE ne de MSG_NOSIGNAL kullanılabilir olduğunda ben Solaris durum için olası bir çözüm nitelendirdi.

Bunun yerine, kitaplık kodunu çalıştıran geçerli iş parçacığında SIGPIPE'yi geçici olarak bastırmamız gerekir. Bunu şu şekilde yapabilirsiniz: SIGPIPE'yi bastırmak için önce beklemede olup olmadığını kontrol ederiz. Eğer öyleyse, bu iş parçacığında engellendiği anlamına gelir ve hiçbir şey yapmamalıyız. Kütüphane ek SIGPIPE üretirse, bekleyen kütüphaneyle birleştirilir ve bu işlem yapılmaz. SIGPIPE beklemede değilse, bu iş parçacığında engelleriz ve zaten engellenip engellenmediğini kontrol ederiz. O zaman yazarlarımızı yürütebiliriz. SIGPIPE'i orijinal durumuna geri yükleyeceğimiz zaman aşağıdakileri yaparız: SIGPIPE başlangıçta beklemedeyse hiçbir şey yapmayız. Aksi takdirde, beklemede olup olmadığını kontrol ederiz. Varsa (bu, dışarıdaki işlemlerin bir veya daha fazla SIGPIPE ürettiği anlamına gelir), bu iş parçacığında bunu bekleriz, böylece beklemedeki durumunu (bunu yapmak için sıfır zaman aşımı ile sigtimedwait () kullanırız; bu, kötü niyetli kullanıcının SIGPIPE'yi manuel olarak tüm bir sürece gönderdiği bir senaryoda engellenmekten kaçınmak içindir: bu durumda beklemede göreceğiz, ancak diğer iş parçacığı beklemek için bir değişiklik yapmadan önce hallederiz). Bekleyen durumu temizledikten sonra bu iş parçacığında SIGPIPE engellemesini kaldırıyoruz, ancak yalnızca orijinal olarak engellenmemişse.

Https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156 adresindeki örnek kod


22

SIGPIPE'i Yerel Olarak Ele Alma

Hatayı bir global sinyal olay işleyicisinden ziyade yerel olarak ele almak genellikle en iyisidir, çünkü yerel olarak neler olup bittiğine ve hangi başvurunun alınacağına ilişkin daha fazla içeriğiniz olacaktır.

Uygulamalarımdan birinde, uygulamamın harici bir aksesuarla iletişim kurmasını sağlayan bir iletişim katmanım var. Bir yazma hatası meydana geldiğinde, iletişim katmanında istisna atıyorum ve orada işlemek için bir try catch bloğuna kadar kabarmasına izin veriyorum.

Kod:

SIGPIPE sinyalini yerel olarak işleyebilmeniz için yoksayılan kod:

// We expect write failures to occur but we want to handle them where 
// the error occurs rather than in a SIGPIPE handler.
signal(SIGPIPE, SIG_IGN);

Bu kod SIGPIPE sinyalinin yükselmesini önler, ancak soketi kullanmaya çalışırken bir okuma / yazma hatası alırsınız, bu yüzden bunu kontrol etmeniz gerekir.


Bu çağrı bir soket bağlandıktan sonra mı yoksa ondan önce mi yapılmalı? nereye yerleştirmek en iyisidir. Teşekkürler
Ahmed

Ben şahsen koydum @Ahmed applicationDidFinishLaunching: . Ana şey, bir soket bağlantısıyla etkileşime girmeden önce olması gerektiğidir.
Sam

ancak soketi kullanmaya çalışırken bir okuma / yazma hatası alırsınız, bu yüzden bunu kontrol etmeniz gerekir. - Bunun nasıl mümkün olduğunu sorabilir miyim? sinyali (SIGPIPE, SIG_IGN) benim için çalışıyor ancak hata ayıklama modunda bir hata döndürüyor da bu hatayı yok saymak mümkün mü?
jongbanaag

@ Dreyfus15 Ben sadece yerel olarak işlemek için bir try / catch blok soketi kullanarak çağrıları sarılmış inanıyorum. Bunu gördüğümden beri bir süredir oldu.
Sam

15

Bir borunun uzak ucundaki işlemin çıkmasını engelleyemezsiniz ve yazmayı bitirmeden önce çıkarsa, bir SIGPIPE sinyali alırsınız. Sinyali SIG_IGN yaparsanız, yazmanız bir hata ile geri döner - ve bu hatayı not etmeniz ve buna yanıt vermeniz gerekir. Sadece bir işleyicideki sinyali yakalamak ve görmezden gelmek iyi bir fikir değildir - borunun artık geçersiz olduğunu ve programın davranışını değiştirerek, boruya tekrar yazmaması için (sinyal tekrar üretileceğinden ve yok sayıldığından) tekrar deneyin ve tekrar deneyeceksiniz ve tüm süreç uzun süre devam edebilir ve çok fazla CPU gücü harcayabilir).


6

Yoksa SIGPIPE'i bir işleyiciyle yakalayıp görmezden gelmeli miyim?

Bunun doğru olduğuna inanıyorum. Diğer ucun tanımlayıcılarını ne zaman kapattığını bilmek istersiniz ve SIGPIPE size bunu söyler.

Sam


3

Linux kılavuzu şunları söyledi:

EPIPE Yerel uç, bağlantı yönelimli bir sokette kapatılmıştır. Bu durumda, MSG_NOSIGNAL ayarlanmadığı sürece işlem bir SIGPIPE alır.

Ancak Ubuntu 12.04 için doğru değil. Bu dava için bir test yazdım ve her zaman SIGPIPE ile EPIPE alıyorum. Aynı kırık sokete ikinci kez yazmaya çalışırsam SIGPIPE üretilir. Bu nedenle, eğer bu sinyal oluşursa, programınızda mantık hatası anlamına gelen SIGPIPE'ı göz ardı etmenize gerek yoktur.


1
Kesinlikle SIGPIPE'ı linux'ta bir sokette EPIPE görmeden alırım. Bu bir çekirdek hatası gibi geliyor.
davmac

3

Buradaki çarpışmayı önlemek için en iyi uygulama nedir?

Sigpipes'ı herkese göre devre dışı bırakın veya hatayı yakalayıp yok sayın.

Çizginin diğer tarafının hala okunup okunmadığını kontrol etmenin bir yolu var mı?

Evet, select () öğesini kullanın.

select () burada her zaman soketin yazılabilir olduğunu söylediği için çalışmıyor.

Okuma bitleri üzerinde seçim yapmanız gerekir . Muhtemelen yazma bitlerini göz ardı edebilirsiniz .

Uzak uç dosya tanıtıcısını kapattığında, Select seçeneği okumaya hazır verilerin bulunduğunu bildirir. Gittiğinizde ve okuduğunuzda, 0 bayt geri alırsınız, bu da işletim sisteminin size dosya tanıtıcısının kapatıldığını söyler.

Yazma bitlerini göz ardı edemediğiniz tek zaman, büyük hacimler gönderiyorsanız ve diğer ucun yeniden bloke olma riski vardır ve bu da arabelleklerinizin dolmasına neden olabilir. Bu durumda, dosya tanıtıcısına yazmaya çalışmak programınızın / iş parçacığınızın engellenmesine veya başarısız olmasına neden olabilir. Seçimi yazmadan önce test etmek sizi bundan koruyacaktır, ancak diğer ucun sağlıklı olduğunu veya verilerinizin geleceğini garanti etmez.

Close () öğesinden ve yazarken bir sigpipe sahip olabileceğinizi unutmayın.

Kapat, arabelleğe alınan verileri temizler. Diğer uç zaten kapatılmışsa, kapatma başarısız olur ve bir sigpipe alırsınız.

Arabelleklenmiş TCPIP kullanıyorsanız, başarılı bir yazma işlemi verilerinizin gönderilmek üzere kuyruğa alındığı anlamına gelir, bu gönderildiği anlamına gelmez. Başarılı bir şekilde kapatmayı kapatana kadar, verilerinizin gönderildiğini bilmiyorsunuz.

Sigpipe size bir şeyin yanlış gittiğini söyler, size neyi ya da bu konuda ne yapmanız gerektiğini söylemez.


Sigpipe size bir şeyin yanlış gittiğini söyler, size neyi ya da bu konuda ne yapmanız gerektiğini söylemez. Kesinlikle. SIGPIPE'nin neredeyse tek amacı, bir sonraki aşama artık girişe ihtiyaç duymadığında borulu komut satırı yardımcı programlarını yıkmaktır. Programınız ağ iletişimi yapıyorsa, genellikle program genelinde SIGPIPE'yi engellemeniz veya yoksaymanız gerekir.
DepressedDaniel

Tam. SIGPIPE, "find XXX | head" gibi durumlar için tasarlanmıştır. Kafa 10 çizgisiyle eşleştiğinde, bulmaya devam etmenin bir anlamı yoktur. Bu yüzden kafa çıkar ve bir dahaki sefere onunla konuşmaya çalışırsa, bir sigpipe sahip olur ve çıkabileceğini de bilir.
Ben Aveling

Gerçekten, SIGPIPE'nin tek amacı, programların özensiz olmasına ve yazma hatalarını kontrol etmemesine izin vermektir!
William Pursell

2

Modern bir POSIX sistemi (yani Linux) altında bu sigprocmask()işlevi kullanabilirsiniz .

#include <signal.h>

void block_signal(int signal_to_block /* i.e. SIGPIPE */ )
{
    sigset_t set;
    sigset_t old_state;

    // get the current state
    //
    sigprocmask(SIG_BLOCK, NULL, &old_state);

    // add signal_to_block to that existing state
    //
    set = old_state;
    sigaddset(&set, signal_to_block);

    // block that signal also
    //
    sigprocmask(SIG_BLOCK, &set, NULL);

    // ... deal with old_state if required ...
}

Önceki durumu daha sonra geri yüklemek isterseniz, old_stategüvenli bir yeri kaydettiğinizden emin olun . Bu işlevi birden çok kez çağırırsanız, bir yığın kullanmanız veya yalnızca ilk veya sonuncuyu kaydetmeniz gerekirold_state ... veya belirli bir engellenmiş sinyali kaldıran bir işleve sahip .

Daha fazla bilgi için kılavuz sayfasını okuyun .


Bunun gibi engellenmiş sinyaller setini okumak-değiştirmek-yazmak gerekli değildir. sigprocmaskengellenen sinyaller kümesine ekler, böylece tüm bunları tek bir çağrı ile yapabilirsiniz.
Luchs

Ben yok -okuma-yazma değiştirmek , ben tutmak mevcut durumunu kaydetmek için okumak old_stateı seçerseniz ben, daha sonra geri yükleyebilirsiniz bu şekilde böylece. Devleti asla geri yüklemeniz gerekmeyeceğini biliyorsanız, gerçekten de bu şekilde okumaya ve depolamaya gerek yoktur.
Alexis Wilke

1
Ama yapıyorsunuz: sigprocmask()eski durumu okuyan ilk çağrı , sigaddsetonu değiştirme çağrısı sigprocmask(), onu yazan ikinci çağrı . İlk aramayı kaldırabilir, ile başlatabilir sigemptyset(&set)ve ikinci aramayı değiştirebilirsiniz sigprocmask(SIG_BLOCK, &set, &old_state).
Luchs

Ah! Ha ha! Haklısın. Yaparım. Eh ... yazılımımda, zaten hangi sinyalleri engellediğimi veya engellemediğimi bilmiyorum. Ben de bu şekilde yapıyorum. Ancak, sadece bir "set sinyali bu şekilde" varsa, basit bir clear + setinin yeterli olduğunu kabul ediyorum.
Alexis Wilke
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.