Question

J'ai un petit programme serveur qui accepte les connexions sur un socket TCP ou UNIX local, lit une commande simple et, en fonction de la commande, envoie une réponse. Le problème est que le client n'a parfois aucun intérêt dans la réponse et qu'il quitte tôt, donc écrire dans ce socket provoquera un SIGPIPE et fera planter mon serveur. Quelle est la meilleure pratique pour empêcher l'accident ici? Existe-t-il un moyen de vérifier si l’autre côté de la ligne lit encore? (select () ne semble pas fonctionner ici car il dit toujours que le socket est accessible en écriture). Ou devrais-je simplement attraper le SIGPIPE avec un gestionnaire et l’ignorer?

Était-ce utile?

La solution

Vous voulez généralement ignorer le SIGPIPE et gérer l'erreur directement dans votre code. En effet, les gestionnaires de signaux en C ont de nombreuses restrictions sur ce qu’ils peuvent faire.

La méthode la plus portable consiste à définir le gestionnaire SIGPIPE sur SIG_IGN . Cela empêchera toute écriture de socket ou de canal de générer un signal SIGPIPE .

Pour ignorer le signal SIGPIPE , utilisez le code suivant:

signal(SIGPIPE, SIG_IGN);

Si vous utilisez l'appel send () , une autre option consiste à utiliser l'option MSG_NOSIGNAL , qui activera le comportement SIGPIPE . hors appel par appel. Notez que tous les systèmes d'exploitation ne prennent pas en charge l'indicateur MSG_NOSIGNAL .

Enfin, vous pouvez également envisager l’indicateur de socket SO_SIGNOPIPE qui peut être défini avec setsockopt () sur certains systèmes d’exploitation. Cela évitera que SIGPIPE ne soit causé par des écritures que sur les sockets sur lesquels il est configuré.

Autres conseils

Une autre méthode consiste à changer le socket pour qu’il ne génère jamais SIGPIPE sur write (). C’est plus pratique dans les bibliothèques, où vous pourriez ne pas vouloir de gestionnaire de signal global pour SIGPIPE.

Sur la plupart des systèmes BSD (MacOS, FreeBSD ...) (en supposant que vous utilisiez C / C ++), vous pouvez le faire avec:

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

Ainsi, au lieu de générer le signal SIGPIPE, EPIPE sera renvoyé.

Je suis très en retard pour la fête, mais SO_NOSIGPIPE n'est pas portable et risque de ne pas fonctionner sur votre système (il semble que ce soit un problème BSD).

Une bonne alternative si vous êtes sur, par exemple, un système Linux sans SO_NOSIGPIPE serait de définir le drapeau MSG_NOSIGNAL sur votre appel send (2).

Exemple de remplacement de write (...) par send (..., MSG_NOSIGNAL) (voir commentaire de nobar )

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

Dans cet post , j'ai décrit une solution possible pour Cas Solaris lorsque ni SO_NOSIGPIPE ni MSG_NOSIGNAL ne sont disponibles.

  

Au lieu de cela, nous devons supprimer temporairement SIGPIPE dans le thread actuel qui exécute le code de bibliothèque. Voici comment procéder: pour supprimer SIGPIPE, nous vérifions d’abord si celui-ci est en attente. Si c'est le cas, cela signifie qu'il est bloqué dans ce fil et que nous ne devons rien faire. Si la bibliothèque génère un SIGPIPE supplémentaire, il sera fusionné avec celui qui est en attente, ce qui n’est pas une opération. Si SIGPIPE n'est pas en attente, nous le bloquons dans ce thread et vérifions également s'il était déjà bloqué. Ensuite, nous sommes libres d'exécuter nos écritures. Lorsque nous devons restaurer SIGPIPE à son état d'origine, nous procédons comme suit: si SIGPIPE était en attente à l'origine, nous ne faisons rien. Sinon, nous vérifions s'il est en attente maintenant. Si c'est le cas (ce qui signifie que les actions out ont généré un ou plusieurs SIGPIPE), nous l'attendons dans ce fil, effaçant ainsi son statut en attente (pour ce faire, nous utilisons sigtimedwait () avec un délai d'expiration égal; ceci afin d'éviter tout blocage un scénario dans lequel un utilisateur malveillant a envoyé SIGPIPE manuellement à l’ensemble du processus: dans ce cas, nous le verrons en attente, mais un autre thread pourra le gérer avant que nous attendions un changement pour l’attendre). Après avoir effacé le statut en attente, nous débloquons SIGPIPE dans ce fil, mais uniquement s’il n’était pas bloqué à l’origine.

Exemple de code à l'adresse https://github.com/krok /XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156

Traiter SIGPIPE localement

Il est généralement préférable de gérer l'erreur localement plutôt que dans un gestionnaire d'événement de signal global, car localement, vous aurez plus de contexte pour ce qui se passe et quels recours utiliser.

J'ai une couche de communication dans l'une de mes applications qui permet à mon application de communiquer avec un accessoire externe. Lorsqu’une erreur d’écriture survient, je jette une exception dans la couche communication et la laisse bouger jusqu’à un bloc catch catch pour la gérer.

Code:

Le code pour ignorer un signal SIGPIPE afin que vous puissiez le gérer localement est le suivant:

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

Ce code empêchera le signal SIGPIPE d'être élevé, mais vous obtiendrez une erreur de lecture / écriture lors de la tentative d'utilisation du socket. Vous devrez donc vérifier cela.

Vous ne pouvez pas empêcher le processus situé à l'extrémité d'un canal de se fermer. S'il se termine avant l'écriture, vous obtenez un signal SIGPIPE. Si vous SIG_IGN le signal, votre écriture retournera avec une erreur - et vous devez noter et réagir à cette erreur. Attraper et ignorer le signal dans un gestionnaire n'est pas une bonne idée - vous devez noter que le canal est maintenant obsolète et modifier le comportement du programme pour qu'il n'écrit plus dans le canal (car le signal sera généré à nouveau et ignoré encore une fois, et vous allez réessayer, et le processus entier risque de durer longtemps et de gaspiller beaucoup de puissance de calcul).

  

Ou devrais-je simplement attraper le SIGPIPE avec un gestionnaire et l'ignorer?

Je crois que tout va bien. Vous voulez savoir quand l'autre partie a fermé son descripteur et c'est ce que SIGPIPE vous dit.

Sam

Le manuel Linux disait:

  

EPIPE L’extrémité locale a été fermée sur une connexion orientée                 prise. Dans ce cas, le processus recevra également un SIGPIPE                 sauf si MSG_NOSIGNAL est défini.

Mais pour Ubuntu 12.04, ce n’est pas correct. J'ai écrit un test pour ce cas et je reçois toujours EPIPE sans SIGPIPE. SIGPIPE est généré si j'essaie d'écrire sur le même socket cassé une seconde fois. Donc, vous n'avez pas besoin d'ignorer SIGPIPE si ce signal se produit, cela signifie une erreur de logique dans votre programme.

  

Quelle est la meilleure pratique pour éviter l'accident ici?

Désactivez les sigpipes comme tout le monde ou interceptez et ignorez l'erreur.

  

Existe-t-il un moyen de vérifier si l’autre côté de la ligne lit encore?

Oui, utilisez select ().

  

select () ne semble pas fonctionner ici car il dit toujours que le socket est accessible en écriture.

Vous devez sélectionner les bits lire . Vous pouvez probablement ignorer les bits write .

Lorsque l'extrémité distante ferme son descripteur de fichier, select vous indiquera que des données sont prêtes à être lues. Lorsque vous lirez cela, vous obtiendrez 0 octet, ce qui explique comment le système d’exploitation vous indique que le descripteur de fichier a été fermé.

La seule fois où vous ne pouvez pas ignorer les bits d’écriture, c’est si vous envoyez de gros volumes et que l’autre extrémité risque de devenir en attente de traitement, ce qui peut entraîner le remplissage de vos mémoires tampons. Si cela se produit, alors essayer d'écrire dans le descripteur de fichier peut bloquer ou échouer votre programme / thread. Tester select avant d’écrire vous protégera contre cela, mais cela ne garantit pas que l’autre extrémité est saine ni que vos données vont arriver.

Notez que vous pouvez obtenir un sigpipe de close (), ainsi que lorsque vous écrivez.

Fermer efface toutes les données mises en mémoire tampon. Si l'autre extrémité a déjà été fermée, la fermeture échouera et vous recevrez un sigpipe.

Si vous utilisez TCPIP en mémoire tampon, une écriture réussie signifie simplement que vos données ont été mises en file d'attente pour être envoyées, cela ne signifie pas pour autant qu'elles ont été envoyées. Jusqu'à ce que vous appeliez close, vous ne savez pas que vos données ont été envoyées.

Sigpipe vous dit que quelque chose ne va pas, il ne vous dit pas quoi ou ce que vous devriez faire à ce sujet.

Sous un système POSIX moderne (Linux), vous pouvez utiliser la fonction 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 ...
}

Si vous souhaitez restaurer l'état précédent ultérieurement, veillez à enregistrer le old_state dans un endroit sûr. Si vous appelez cette fonction plusieurs fois, vous devez utiliser une pile ou enregistrer uniquement le premier ou le dernier old_state ... ou peut-être une fonction qui supprime un signal bloqué spécifique.

Pour plus d'informations, consultez la page de manuel .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top