Domanda

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 perché dice sempre che il socket è scrivibile). O dovrei semplicemente prendere SIGPIPE con un gestore e ignorarlo?

È stato utile?

Soluzione

In genere si desidera ignorare il SIGPIPE e gestire l'errore direttamente nel proprio 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 gestore SIGPIPE su SIG_IGN . Ciò impedirà a qualsiasi scrittura di socket o pipe di causare un segnale SIGPIPE .

Per ignorare il segnale SIGPIPE , utilizzare il seguente codice:

signal(SIGPIPE, SIG_IGN);

Se stai usando la chiamata send () , un'altra opzione è usare l'opzione MSG_NOSIGNAL , che trasformerà il comportamento SIGPIPE spento in base alla chiamata. Nota che non tutti i sistemi operativi supportano il flag MSG_NOSIGNAL .

Infine, potresti anche considerare il flag SO_SIGNOPIPE che può essere impostato con setsockopt () su alcuni sistemi operativi. Ciò eviterà che SIGPIPE sia causato da scritture nei socket su cui è impostato.

Altri suggerimenti

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 usando C / C ++), puoi farlo con:

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

Con questo in effetti, invece del segnale SIGPIPE generato, verrà restituito EPIPE.

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

Una buona alternativa se, ad esempio, sei su un sistema Linux senza SO_NOSIGPIPE sarebbe impostare il flag MSG_NOSIGNAL sulla tua chiamata send (2).

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

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

In questo post ho descritto una possibile soluzione per 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. Se lo fa (il che significa che le azioni out hanno generato uno o più SIGPIPE), quindi 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 per aspettarlo). Dopo aver cancellato lo stato in sospeso, sblocciamo SIGPIPE in questo thread, ma solo se non è stato bloccato originariamente.

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

Gestisci 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, lancio un'eccezione nel livello di comunicazione e lascio passare il mouse su un blocco catch tentativo di gestirlo lì.

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.

Non puoi impedire che il processo all'estremità remota di una pipe esca e se esce prima che tu abbia finito di scrivere, otterrai 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 per lungo e sprecare molta potenza della CPU.

  

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

Il manuale di Linux diceva:

  

EPIPE L'estremità locale è stata chiusa su una connessione orientata                 zoccolo. 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.

  

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

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.

Devi selezionare i bit leggi . Probabilmente puoi ignorare i bit write .

Quando l'estremità lontana chiude il suo handle di file, selezionare ti dirà che ci sono dati pronti per la lettura. Quando vai a leggerlo, otterrai 0 byte, il 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 sull'handle del file può causare il blocco o il fallimento del programma / thread. Testare la selezione prima di scrivere ti proteggerà da ciò, 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 cancella tutti i dati bufferizzati. Se l'altra estremità è già stata chiusa, la chiusura fallirà e riceverai un sigpipe.

Se si utilizza TCPIP con buffer, 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 avrai chiuso con successo, non sai che i tuoi dati sono stati inviati.

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

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

#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 vuoi ripristinare lo stato precedente in un secondo momento, assicurati di salvare old_state in un posto sicuro. Se chiamate quella funzione più volte, dovete usare 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 .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top