Come prevenire SIGPIPE (o gestirli correttamente)


261

Ho un piccolo programma server che accetta connessioni su un socket TCP o UNIX locale, legge un comando semplice e, a seconda del comando, invia una risposta. Il problema è che il client potrebbe non avere interesse per la risposta a volte ed uscire presto, quindi scrivere su quel socket causerà un SIGPIPE e causerà il crash del mio server. Qual è la migliore pratica per prevenire l'incidente qui? C'è un modo per verificare se l'altro lato della riga sta ancora leggendo? (select () non sembra funzionare qui come dice sempre che il socket è scrivibile). O dovrei semplicemente prendere SIGPIPE con un gestore e ignorarlo?

Risposte:


255

In genere si desidera ignorare SIGPIPEe gestire l'errore direttamente nel codice. Questo perché i gestori di segnali in C hanno molte restrizioni su ciò che possono fare.

Il modo più portatile per farlo è impostare il SIGPIPEgestore su SIG_IGN. Ciò impedirà a qualsiasi scrittura su socket o pipe di provocare un SIGPIPEsegnale.

Per ignorare il SIGPIPEsegnale, utilizzare il seguente codice:

signal(SIGPIPE, SIG_IGN);

Se si utilizza la send()chiamata, un'altra opzione è quella di utilizzare l' MSG_NOSIGNALopzione, che disattiverà il SIGPIPEcomportamento in base alla chiamata. Si noti che non tutti i sistemi operativi supportano il MSG_NOSIGNALflag.

Infine, potresti anche considerare il SO_SIGNOPIPEflag socket che può essere impostato setsockopt()su alcuni sistemi operativi. Ciò eviterà SIGPIPEdi essere causato da scritture solo sui socket su cui è impostato.


75
Per rendere esplicito questo: segnale (SIGPIPE, SIG_IGN);
jhclark

5
Ciò può essere necessario se ricevi un codice di ritorno uscita di 141 (128 + 13: SIGPIPE). SIG_IGN è il gestore del segnale ignore.
velcrow

6
Per i socket, è davvero più facile usare send () con MSG_NOSIGNAL, come diceva @talash.
Pawel Veselov,

3
Cosa succede esattamente se il mio programma scrive su una pipe rotta (un socket nel mio caso)? SIG_IGN fa sì che il programma ignori SIG_PIPE, ma ciò comporta che send () fa qualcosa di indesiderabile?
sudo,

2
Vale la pena menzionare, poiché l'ho trovato una volta, poi l'ho dimenticato in seguito e mi sono confuso, quindi l'ho scoperto una seconda volta. I debugger (so che gdb lo fa) hanno la precedenza sulla gestione del segnale, quindi i segnali ignorati non vengono ignorati! Testa il tuo codice al di fuori di un debugger per assicurarti che SIGPIPE non si verifichi più. stackoverflow.com/questions/6821469/…
Jetski S-type

155

Un altro metodo è quello di cambiare il socket in modo che non generi mai SIGPIPE su write (). Questo è più conveniente nelle librerie, dove potresti non voler un gestore di segnale globale per SIGPIPE.

Sulla maggior parte dei sistemi basati su BSD (MacOS, FreeBSD ...), (supponendo che tu stia utilizzando C / C ++), puoi farlo con:

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

Con questo in effetti, anziché generare il segnale SIGPIPE, verrà restituito EPIPE.


45
Sembra buono, ma SO_NOSIGPIPE non sembra esistere in Linux, quindi non è una soluzione ampiamente portatile.
nobar,

1
ciao a tutti, puoi dirmi dove devo inserire questo codice? non capisco grazie
R. Dewi,

9
In Linux, vedo l'opzione MSG_NOSIGNAL nelle bandiere di invio
Aadishri,

Funziona perfettamente su Mac OS X
Kirugan

116

Sono in ritardo alla festa, ma SO_NOSIGPIPEnon è portatile e potrebbe non funzionare sul tuo sistema (sembra essere una cosa BSD).

Una buona alternativa se sei su un sistema Linux senza, SO_NOSIGPIPEsarebbe quello di impostare il MSG_NOSIGNALflag sulla tua chiamata send (2).

Esempio di sostituzione write(...)con send(...,MSG_NOSIGNAL)(vedi il commento di nobar )

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

5
In altre parole, usa send (..., MSG_NOSIGNAL) in sostituzione di write () e non otterrai SIGPIPE. Questo dovrebbe funzionare bene per i socket (su piattaforme supportate), ma send () sembra essere limitato all'uso con socket (non pipe), quindi questa non è una soluzione generale al problema SIGPIPE.
nobar

@ Ray2k Chi diavolo sta ancora sviluppando applicazioni per Linux <2.2? Questa è in realtà una domanda semi-seria; la maggior parte delle distro spedisce con almeno 2.6.
Parthian Shot,

1
@Parthian Shot: dovresti ripensare la tua risposta. La manutenzione di vecchi sistemi embedded è un motivo valido per prendersi cura delle versioni precedenti di Linux.
Kirsche40,

1
@ kirsche40 Non sono l'OP, ero solo curioso.
Parthian Shot

@sklnd ha funzionato perfettamente per me. Sto usando un QTcpSocketoggetto Qt per concludere l'utilizzo dei socket, ho dovuto sostituire la writechiamata del metodo con un sistema operativo send(usando il socketDescriptormetodo). Qualcuno conosce un'opzione più pulita per impostare questa opzione in una QTcpSocketclasse?
Emilio González Montaña,

29

In questo post ho descritto la possibile soluzione per il caso Solaris quando non sono disponibili né SO_NOSIGPIPE né MSG_NOSIGNAL.

Invece, dobbiamo sopprimere temporaneamente SIGPIPE nel thread corrente che esegue il codice della libreria. Ecco come fare: per sopprimere SIGPIPE controlliamo prima se è in sospeso. In tal caso, ciò significa che è bloccato in questo thread e non dobbiamo fare nulla. Se la libreria genera SIGPIPE aggiuntivo, verrà unita a quella in sospeso, e questa è una no-op. Se SIGPIPE non è in sospeso, lo blocciamo in questo thread e controlliamo anche se è già stato bloccato. Quindi siamo liberi di eseguire le nostre scritture. Quando vogliamo ripristinare SIGPIPE al suo stato originale, facciamo quanto segue: se SIGPIPE era in attesa originariamente, non facciamo nulla. Altrimenti controlliamo se è in sospeso ora. In caso affermativo (il che significa che le nostre azioni hanno generato uno o più SIGPIPE), lo aspettiamo in questo thread, cancellando così il suo stato in sospeso (per fare ciò usiamo sigtimedwait () con zero timeout; questo per evitare il blocco in uno scenario in cui un utente malintenzionato ha inviato SIGPIPE manualmente a un intero processo: in questo caso lo vedremo in sospeso, ma altri thread potrebbero gestirlo prima che avessimo una modifica ad aspettarlo). Dopo aver cancellato lo stato in sospeso, sblocciamo SIGPIPE in questo thread, ma solo se non era bloccato originariamente.

Codice di esempio su https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156


22

Gestire SIGPIPE localmente

Di solito è meglio gestire l'errore localmente piuttosto che in un gestore di eventi di segnale globale poiché localmente avrai più contesto su cosa sta succedendo e quale ricorso intraprendere.

Ho uno strato di comunicazione in una delle mie app che consente alla mia app di comunicare con un accessorio esterno. Quando si verifica un errore di scrittura, ho generato un'eccezione nel livello di comunicazione e ho lasciato che si formasse un blocco catch per tentarlo.

Codice:

Il codice per ignorare un segnale SIGPIPE in modo da poterlo gestire localmente è:

// 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);

Questo codice impedirà l'innalzamento del segnale SIGPIPE, ma si verificherà un errore di lettura / scrittura quando si tenta di utilizzare il socket, quindi è necessario verificarlo.


questa chiamata deve essere effettuata dopo aver collegato una presa o prima? dove è meglio collocare. Grazie
Ahmed,

@Ahmed Io personalmente messo in applicationDidFinishLaunching: . La cosa principale è che dovrebbe essere prima di interagire con una connessione socket.
Sam,

ma si verificherà un errore di lettura / scrittura quando si tenta di utilizzare il socket, quindi è necessario verificarlo. - Posso chiederti come è possibile? signal (SIGPIPE, SIG_IGN) funziona per me ma in modalità debug restituisce un errore è possibile ignorarlo?
jongbanaag,

@ Dreyfus15 Credo di aver appena terminato le chiamate usando il socket in un blocco try / catch per gestirlo localmente. È passato un po 'di tempo da quando l'ho visto.
Sam,

15

Non è possibile impedire che il processo all'estremità remota di una pipe esca e, se termina prima di aver finito di scrivere, si riceverà un segnale SIGPIPE. Se SIG_IGN il segnale, la tua scrittura tornerà con un errore - e devi annotare e reagire a quell'errore. Catturare e ignorare il segnale in un gestore non è una buona idea - è necessario notare che la pipe è ora defunta e modificare il comportamento del programma in modo che non scriva di nuovo sulla pipe (perché il segnale verrà generato di nuovo e ignorato di nuovo, e ci riproverai, e l'intero processo potrebbe continuare a lungo e sprecare molta potenza della CPU).


6

O dovrei semplicemente prendere SIGPIPE con un gestore e ignorarlo?

Credo che sia giusto. Vuoi sapere quando l'altra estremità ha chiuso il loro descrittore ed è quello che ti dice SIGPIPE.

Sam


3

Il manuale di Linux diceva:

EPIPE L'estremità locale è stata chiusa su un socket orientato alla connessione. In questo caso il processo riceverà anche un SIGPIPE a meno che non sia impostato MSG_NOSIGNAL.

Ma per Ubuntu 12.04 non è giusto. Ho scritto un test per quel caso e ricevo sempre EPIPE senza SIGPIPE. SIGPIPE viene generato se provo a scrivere una seconda volta sullo stesso socket rotto. Quindi non è necessario ignorare SIGPIPE se questo segnale si verifica significa errore logico nel programma.


1
Sicuramente ricevo SIGPIPE senza prima vedere EPIPE su un socket in Linux. Sembra un bug del kernel.
davmac,

3

Qual è la migliore pratica per prevenire l'incidente qui?

O disabilita i sigpipes come per tutti, oppure cattura e ignora l'errore.

C'è un modo per verificare se l'altro lato della riga sta ancora leggendo?

Sì, utilizzare select ().

select () non sembra funzionare qui poiché dice sempre che il socket è scrivibile.

È necessario selezionare i bit letti . Probabilmente puoi ignorare la scrittura bit di .

Quando l'estremità remota chiude l'handle del file, selezionare seleziona per indicare che ci sono dati pronti per la lettura. Quando vai a leggerlo, otterrai 0 byte, che è come il sistema operativo ti dice che l'handle del file è stato chiuso.

L'unica volta che non puoi ignorare i bit di scrittura è se stai inviando grandi volumi e c'è il rischio che l'altra estremità venga backlog, il che può causare il riempimento dei buffer. In tal caso, provare a scrivere nell'handle del file può causare il blocco o il fallimento del programma / thread. Testare la selezione prima di scrivere ti proteggerà da questo, ma non garantisce che l'altra estremità sia integra o che i tuoi dati arriveranno.

Nota che puoi ottenere un sigpipe da close (), così come quando scrivi.

Chiudi elimina tutti i dati bufferizzati. Se l'altra estremità è già stata chiusa, la chiusura fallirà e riceverai un sigpipe.

Se si utilizza il TCPIP bufferizzato, una scrittura corretta significa solo che i dati sono stati messi in coda per l'invio, non significa che siano stati inviati. Fino a quando non si chiude correttamente, non si sa che i dati sono stati inviati.

Sigpipe ti dice che qualcosa è andato storto, non ti dice cosa o cosa dovresti fare al riguardo.


Sigpipe ti dice che qualcosa è andato storto, non ti dice cosa o cosa dovresti fare al riguardo. Esattamente. Praticamente l'unico scopo di SIGPIPE è abbattere i programmi di utilità della riga di comando quando la fase successiva non ha più bisogno dell'input. Se il tuo programma funziona in rete, di solito dovresti semplicemente bloccare o ignorare SIGPIPE a livello di programma.
Depresso

Precisamente. SIGPIPE è destinato a situazioni come "trova XXX | testa". Una volta che la testa ha abbinato le sue 10 linee, non ha senso continuare con la ricerca. Quindi la testa esce, e la prossima volta che trova cerca di parlarci, ottiene un sigpipe e sa che anche lui può uscire.
Ben Aveling,

Davvero, l'unico scopo di SIGPIPE è quello di consentire ai programmi di essere sciatti e di non verificare la presenza di errori nelle scritture!
William Pursell,

2

In un moderno sistema POSIX (ad esempio Linux), è possibile utilizzare la sigprocmask()funzione.

#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 ...
}

Se desideri ripristinare lo stato precedente in un secondo momento, assicurati di salvarlo in un old_stateposto sicuro. Se si chiama quella funzione più volte, è necessario utilizzare uno stack o salvare solo il primo o l'ultimo old_state... o forse avere una funzione che rimuove un segnale bloccato specifico.

Per maggiori informazioni leggi la pagina man .


Non è necessario leggere, modificare, scrivere l'insieme di segnali bloccati come questo. sigprocmaskaggiunge al set di segnali bloccati, quindi puoi fare tutto questo con una sola chiamata.
Luchs

Non leggo-modifica-scrivi , leggo per salvare lo stato corrente che tengo in old_statemodo da poterlo ripristinare in seguito, se lo scelgo. Se sai che non avrai mai bisogno di ripristinare lo stato, non c'è davvero bisogno di leggerlo e memorizzarlo in questo modo.
Alexis Wilke,

1
Ma lo fai: la prima chiamata a sigprocmask()leggere il vecchio stato, la chiamata a sigaddsetmodificarlo, la seconda a sigprocmask()scriverlo. È possibile rimuovere la prima chiamata, inizializzare con sigemptyset(&set)e modificare la seconda chiamata in sigprocmask(SIG_BLOCK, &set, &old_state).
Luchs,

Ah! Ah ah! Hai ragione. Lo voglio. Bene ... nel mio software, non so quali segnali ho già bloccato o non bloccato. Quindi lo faccio in questo modo. Tuttavia, concordo sul fatto che se si dispone di un solo "set di segnali in questo modo", è sufficiente un semplice set + chiaro.
Alexis Wilke,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.