12

Lorsque vous lisez un socket TCP fermé, vous obtenez une erreur régulière, c'est-à-dire qu'il renvoie 0 indiquant EOF ou -1 et un code d'erreur dans errno qui peut être imprimé avec perror.Pourquoi écrire un socket TCP fermé est-il pire que d'en lire un?

Cependant, lorsque vous écrivez un socket TCP fermé, le système d'exploitation envoie SIGPIPE à votre application, ce qui mettra fin à l'application si elle n'est pas interceptée. Pourquoi écrire le socket TCP fermé est-il pire que de le lire?

+0

Il se passe ici quelque chose d'assez subtil: une connexion TCP peut être à moitié fermée, ce qui signifie qu'un côté a fermé le socket (envoyé un paquet FIN), mais que l'autre côté a encore des données à envoyer. Si vous allez à ce niveau, s'il vous plaît lisez: http://superuser.com/questions/298919/what-is-tcp-half-open-connection-and-tcp-half-closed-connection – rbp

Répondre

12

+1 À Greg Hewgill pour diriger mon processus de pensée dans la bonne direction pour trouver la réponse.

La véritable raison de SIGPIPE dans les sockets et les canalisations est l'idiome/modèle de filtre qui s'applique aux E/S typiques dans les systèmes Unix.

À partir de tubes. Les programmes de filtrage comme grep écrivent généralement à STDOUT et lisent à partir de STDIN, qui peuvent être redirigés par le shell vers un canal. Par exemple:

cat someVeryBigFile | grep foo | doSomeThingErrorProne 

quand le réservoir fourches puis ces programmes de exec utilise probablement l'appel système dup2 à redirigent STDIN, STDOUT et STDERR aux tuyaux appropriés. Puisque le programme de filtrage grep ne sait pas et n'a aucun moyen de savoir que sa sortie a été redirigée alors la seule façon de lui dire d'arrêter d'écrire sur un tube rompu si doSomeThingErrorProne se bloque avec un signal puisque les valeurs de retour de écrit au STDOUT sont rarement vérifiées. L'analogue avec les sockets serait le serveur inetd prenant la place de la coquille.

À titre d'exemple, je suppose que vous pouvez transformer grep en un service réseau qui fonctionne sur TCP sockets. Par exemple avec inetd si vous voulez avoir un serveur grep sur TCP port 8000 puis l'ajouter à /etc/services:

grep  8000/tcp # grep server 

Puis ajoutez ceci /etc/inetd.conf:

grep stream tcp nowait root /usr/bin/grep grep foo 

Envoyer SIGHUP-inetd et se connecter au port 8000 avec telnet. Cela devrait provoquer inetd à fork, dup la douille sur STDIN, STDOUT et STDERR et ensuite exec grep avec foo comme argument. Si vous commencez à taper des lignes dans telnet grep fera écho aux lignes qui contiennent foo.

Remplacez maintenant telnet par un programme nommé ticker qui écrit par exemple un flux de cotations boursières en temps réel à STDOUT et obtient des commandes sur STDIN.Quelqu'un se connecte au port 8000 et tape "start java" pour obtenir des devis pour Sun Microsystems. Puis ils se lèvent et vont déjeuner. telnet se bloque inexplicablement. S'il n'y avait aucun SIGPIPE à envoyer alors ticker continuerait à envoyer des citations pour toujours, ne sachant jamais que le processus à l'autre bout s'était écrasé, et gaspillant inutilement des ressources de système.

10

Généralement, si vous écrivez sur une socket, vous vous attendez à ce que l'autre extrémité soit à l'écoute. C'est un peu comme un appel téléphonique - si vous parlez, vous ne vous attendez pas à ce que l'autre interlocuteur raccroche.

Si vous lisez depuis un socket, vous attendez que l'autre extrémité vous envoie (a) quelque chose, ou (b) ferme le socket. La situation (b) se produirait si vous venez d'envoyer quelque chose comme une commande QUIT à l'autre extrémité.

+0

Mais ça ne me dit pas vraiment pourquoi 'write' ou' send' ne peut pas retourner une erreur directement de la même façon que 'read' ou' recv'. Pourquoi frapper l'application sur la tête avec «SIGPIPE» comme ça? Il doit y avoir une raison plus profonde pour une réponse aussi extrême par le système d'exploitation. Dites que j'ai un socket qui vient de recevoir un 'RST'. Si je le "lis", je gagne -1 avec ECONNRESET, pourquoi ne pas avoir la même chose quand j'écris? Dans les deux cas, je m'attends à participer à des E/S consensuelles et à ne pas obtenir ce que j'attendais. –

+6

@Robert: Le cas type d'utilisation de l'entrée et de la sortie sous Unix était historiquement pour les programmes "filtre", qui lisaient depuis un canal d'entrée et écrivaient dans un canal de sortie (le programme 'grep' en est un exemple). Afin que ce filtre se termine immédiatement lorsque la sortie n'écoute plus, le comportement par défaut du signal 'SIGPIPE' a été défini pour terminer le programme. Sans cela, le filtre continuerait à écrire jusqu'à la sortie jusqu'à ce que son entrée soit épuisée (ce qui pourrait prendre un certain temps). –

+2

Dites-moi si cela semble correct: La véritable raison de 'SIGPIPE' est que les programmes de filtrage comme grep écrivent typiquement' STDOUT', qui peut être redirigé par le shell vers un tube. Puisque le programme de filtrage ne sait pas et n'a aucun moyen de savoir que sa sortie a été redirigée, la seule façon de lui dire d'arrêter d'écrire sur un tube rompu est un signal puisque les valeurs de retour des écritures sur 'STDOUT' sont rarement vérifiées . L'analogue avec les sockets serait 'inetd' acceptant une connexion, engendrant le serveur et' dup2' ing le socket sur 'STDIN',' STDOUT', 'STDERR'! –

3

Je pense qu'une grande partie de la réponse est 'de sorte qu'un socket se comporte plutôt comme un tuyau Unix (anonyme) classique'. Ceux-ci présentent également le même comportement - témoin le nom du signal. Par conséquent, il est raisonnable de se demander pourquoi les tuyaux se comportent de cette façon. La réponse de Greg Hewgill donne un résumé de la situation.

Une autre façon de le regarder est - quelle est l'alternative? Est-ce qu'un 'read()' sur un tube sans script donne un signal SIGPIPE? La signification de SIGPIPE devrait changer de 'écrire sur un tuyau avec personne pour le lire', bien sûr, mais c'est trivial. Il n'y a pas de raison particulière de penser que ce serait mieux; l'indication EOF (zéro octet à lire, zéro octet lu) est une description parfaite de l'état de la conduite, et donc le comportement de lecture est bon.

Qu'en est-il de 'write()'? Eh bien, une option serait de retourner le nombre d'octets écrits - zéro. Mais ce n'est pas une bonne idée. cela implique que le code devrait réessayer et peut-être que plus d'octets seraient envoyés, ce qui ne sera pas le cas. Une autre option serait une erreur - write() renvoie -1 et définit un errno approprié. Ce n'est pas clair qu'il y en a un. EINVAL ou EBADF sont tous les deux inexacts: le descripteur de fichier est correct et ouvert à cette fin (et devrait être fermé après l'écriture défaillante); il n'y a tout simplement rien pour le lire. EPIPE signifie «PIPE cassé»; donc, avec une mise en garde à propos de "ceci est une douille, pas un tuyau", ce serait l'erreur appropriée. C'est probablement l'errno renvoyé si vous ignorez SIGPIPE. Il serait possible de le faire - il suffit de renvoyer une erreur appropriée lorsque le tuyau est cassé (et ne jamais envoyer le signal). Cependant, c'est un fait empirique que de nombreux programmes ne prêtent pas autant d'attention à la destination de leur sortie et si vous passez une commande qui lira un fichier de plusieurs gigaoctets dans un processus qui se ferme après les 20 premiers Ko, mais ne fait pas attention à l'état de ses écritures, alors il faudra beaucoup de temps pour finir, et va gaspiller l'effort de la machine en faisant cela, alors qu'en lui envoyant un signal qu'il n'ignore pas, il s'arrêtera rapidement - ceci est certainement avantageux. Et vous pouvez obtenir l'erreur si vous le souhaitez. Ainsi, l'envoi de signaux a des avantages pour les o/s dans le contexte des tuyaux; et les douilles imitent les tuyaux plutôt étroitement.

Intéressant côté: tout en vérifiant le message pour SIGPIPE, j'ai trouvé l'option de socket:

#define SO_NOSIGPIPE 0x1022 /* APPLE: No SIGPIPE on EPIPE */ 
+0

Donc, fondamentalement, vous dites que 'SIGPIPE' existe parce que beaucoup de programmeurs ignorent les codes d'erreur dans le cas de l'écriture, ce qui pourrait causer au processus d'accaparer les ressources du système alors qu'il ne réalise rien? Ou, pour le dire autrement, les gens sont plus méticuleux à propos de la vérification de leur entrée que de leur sortie et c'est la raison de l'asymétrie de 'read' et' write '? –

+0

@Robert: oui, fondamentalement. Les gens ont tendance à écrire leur code en supposant que le périphérique de sortie ne va pas disparaître, ou manquer d'espace. Lorsque la sortie est un tube et que le programme de réception arrête de lire avant la fin de la sortie, il est important de s'assurer que le programme d'écriture fait attention. Et c'est un mécanisme simple qui laisse les programmes plus simples à écrire. –

+0

Donc, y avait-il un temps qui était pré-daté «SIGPIPE»? Puisque vous dites que ceci est dans une certaine mesure le résultat d'un mauvais comportement de l'utilisateur/programmeur, y avait-il une version d'Unix qui renvoyait une erreur lors de l'écriture sur un canal fermé, puis la changeait pour retourner un signal ou était 'SIGPIPE «là depuis le début en prévision d'un mauvais comportement? –

7

Pensez à la prise comme un grand pipeline de données entre l'envoi et le processus de réception. Maintenant, imaginez que le pipeline a une vanne qui est fermée (la connexion de la prise est fermée).

Si vous lisez depuis le socket (en essayant de sortir quelque chose du tuyau), il n'y a aucun mal à essayer de lire quelque chose qui n'est pas là; vous n'obtiendrez pas de données. En fait, vous pouvez, comme vous l'avez dit, obtenir un EOF, ce qui est correct, car il n'y a plus de données à lire.

Cependant, écrire à cette connexion fermée est une autre question. Les données ne passeront pas, et vous pourriez finir par laisser tomber une communication importante sur le sol. (Vous ne pouvez pas envoyer d'eau dans un tuyau avec une valve fermée, si vous essayez, quelque chose va probablement éclater quelque part, ou, à tout le moins, la contre-pression va pulvériser de l'eau partout.) Voilà pourquoi il y a un outil pour vous alerter de cette condition, à savoir, le signal SIGPIPE.

Vous pouvez toujours ignorer ou bloquer le signal, mais vous le faites à vos risques et périls.

Questions connexes