Comment réparer java.net.SocketException: tuyau cassé?


150

J'utilise le client http apache commons pour appeler l'URL en utilisant la méthode post pour publier les paramètres et il lance rarement l'erreur ci-dessous.

java.net.SocketException: Broken pipe
        at java.net.SocketOutputStream.socketWrite0(Native Method)
        at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
        at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
        at java.io.BufferedOutputStream.write(BufferedOutputStream.java:105)
        at java.io.FilterOutputStream.write(FilterOutputStream.java:80)
        at org.apache.commons.httpclient.methods.ByteArrayRequestEntity.writeRequest(ByteArrayRequestEntity.java:90)
        at org.apache.commons.httpclient.methods.EntityEnclosingMethod.writeRequestBody(EntityEnclosingMethod.java:499)
        at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2114)
        at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096)
        at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:398)

Quelqu'un peut-il suggérer ce qui cause cette exception et comment la déboguer?


Réponses:


81

Ceci est causé par:

  • le plus souvent, écrire dans une connexion lorsque l'autre extrémité l'a déjà fermée;
  • moins généralement, le pair fermant la connexion sans lire toutes les données qui sont déjà en attente à sa fin.

Donc, dans les deux cas, vous avez un protocole d'application mal défini ou implémenté.

Il y a une troisième raison que je ne vais pas documenter ici, mais qui implique que le pair prenne des mesures délibérées pour réinitialiser plutôt que fermer correctement la connexion.


4
Pouvez-vous m'aider à l'identifier et à le corriger? Fondamentalement, comment confirmer la raison et la corriger?

4
Je l' ai confirmé la raison. Vous écrivez alors que l'autre extrémité a déjà fermé la connexion. Le correctif n'est pas de faire cela. Comme l'autre extrémité ne le lit pas, cela ne sert à rien de toute façon. Comme je l'ai également dit, si cela se produit, il y a quelque chose qui ne va pas avec la spécification ou l'implémentation de votre protocole d'application, très probablement que vous n'en avez même pas.
Marquis of Lorne

26
Si l'application HTTP côté serveur reçoit des exceptions Broken Pipe, cela signifie simplement que le navigateur client a quitté / est allé sur une autre page / a expiré / est revenu dans l'historique / peu importe. Oublie ça.
Marquis of Lorne

3
Nous avons vu cette exception lors de l'abandon d'une requête ajax dans le navigateur (en utilisant XMLHttpRequest.abort()).
obecker

2
Une cause possible serait un proxy ou un serveur Http fermant la connexion trop tôt. Par exemple, un serveur Apache Http entre votre serveur J2EE et les clients d'application, avec un court délai d'expiration défini. Au moins, c'est ce qui s'est passé dans notre environnement de production. Les clients se plaignent que les pages ne sont pas complètement chargées et nous avons vu ces erreurs sur le journal JBoss. Après quelques tests, nous remarquons que le problème était un serveur Http mal configuré.
Ricardo Vila

32

Dans notre cas, nous en avons fait l'expérience lors d'un test de charge sur notre serveur d'applications. Le problème s'est avéré que nous devions ajouter de la mémoire supplémentaire à notre JVM car elle était à court. Cela a résolu le problème.

Essayez d'augmenter la mémoire disponible pour la JVM et / ou surveillez l'utilisation de la mémoire lorsque vous obtenez ces erreurs.


4
J'ai eu le même problème lors d'un test de charge. Je suppose que les données prennent beaucoup de temps à être générées et que l'outil de test de charge n'attend pas les données assez longtemps avant de fermer la connexion. Dans votre cas, l'ajout de mémoire peut avoir réduit la durée du processus de génération de données, de sorte que le test de charge a obtenu toutes les données sans le délai d'expiration. Une alternative pour augmenter la mémoire aurait été d'augmenter le délai d'expiration de l'outil de test de charge.
Julien Kronegg

2
Il est clair que la mémoire insuffisante a amené l'application à fermer le socket de réception ou même à se fermer complètement, avec le même effet. Une mémoire insuffisante en soi ne provoque pas de rupture de tuyaux.
Marquis of Lorne

1
cela semble également être un problème lors de notre application à forte charge
Ruben de Vries

7

SocketException: tuyau cassé, est provoqué par «l'autre extrémité» (le client ou le serveur) fermant la connexion pendant que votre code lit ou écrit sur la connexion.

Il s'agit d'une exception très courante dans les applications client / serveur qui reçoivent du trafic de clients ou de serveurs en dehors du contrôle d'application. Par exemple, le client est un navigateur. Si le navigateur effectue un appel Ajax et / ou que l'utilisateur ferme simplement la page ou le navigateur, cela peut effectivement tuer toutes les communications de manière inattendue. Fondamentalement, vous verrez cette erreur chaque fois que l'autre extrémité met fin à son application et que vous ne l'aviez pas anticipée.

Si vous rencontrez cette exception dans votre application, cela signifie que vous devez vérifier votre code où l'IO (entrée / sortie) se produit et l'envelopper avec un bloc try / catch pour intercepter cette IOException. C'est alors à vous de décider comment vous voulez gérer cette situation semi-valide.

Dans votre cas, le premier endroit où vous avez encore le contrôle est l'appel à HttpMethodDirector.executeWithRetry- Assurez-vous donc que l'appel est encapsulé avec le bloc try / catch et gérez-le comme bon vous semble.

Je déconseille fortement de consigner les erreurs spécifiques de SocketException-Broken Pipe à autre chose que les niveaux de débogage / trace. Sinon, cela peut être utilisé comme une forme d'attaque DOS (Denial Of Service) en remplissant les journaux. Essayez de renforcer et de tester négativement votre application pour ce scénario courant.


Cela n'est pas causé par le pair fermant la connexion pendant que vous la lisez. Cela provoque une condition différente de fin de flux, qui n'est souvent pas du tout une exception.
Marquis of Lorne

5

Tous les flux et connexions ouverts doivent être correctement fermés, donc la prochaine fois que nous essaierons d'utiliser l'objet urlConnection, cela ne génère pas d'erreur. À titre d'exemple, le changement de code suivant a corrigé l'erreur pour moi.

Avant:

OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
bw.write("Some text");
bw.close();
out.close();

Après:

OutputStream os = urlConnection.getOutputStream();
OutputStream out = new BufferedOutputStream(os);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
bw.write("Some text");
bw.close();
out.close();
os.close(); // This is a must.

8
C'est en fait très étrange, car a fait BufferedOutputStreamtoujours appel close()à son flux de sortie sous-jacent (donc sur le flux de sortie de urlConnection). Voir docs.oracle.com/javase/6/docs/api/java/io/… et docs.oracle.com/javase/6/docs/api/java/io / ... Ainsi, lorsque vous appelez, os.close()il aurait déjà dû être fermé . Non?
obecker

4
«os.close ()» n'est pas «un must». 'Out.close ()' ne l'est pas non plus. 'bw.close ()' est suffisant. @obedker a raison. Ce changement à lui seul ne peut pas avoir résolu le problème.
Marquis of Lorne

1

J'ai implémenté la fonctionnalité de téléchargement de données via le serveur FTP et j'ai trouvé la même exception là aussi lors de la reprise de ce téléchargement. Pour résoudre cette exception, vous devrez toujours vous déconnecter de la session précédente et créer une nouvelle instance du client et une nouvelle connexion avec le serveur. Cette même approche pourrait également être utile pour HTTPClient.


Pour résoudre cette erreur, vous devez éviter d'essayer d'utiliser des sockets fermés.
Marquis de Lorne

3
LOL. Vous pourriez perdre toute la journée à corriger tous les faux conseils sur le SO.
Dave

2
@Dave Indeed. Parfois je fais.
Marquis of Lorne

1

J'avais le même problème pendant que je développais une application Java simple qui écoute sur un TCP spécifique. Habituellement, je n'avais aucun problème, mais lorsque j'ai exécuté un test de résistance, j'ai remarqué qu'une connexion s'était rompue avec une erreur socket write exception.

Après enquête, j'ai trouvé une solution qui résout mon problème. Je sais que cette question est assez ancienne, mais je préfère partager ma solution, quelqu'un peut la trouver utile.

Le problème était sur la création de ServerSocket. J'ai lu de Javadoc qu'il y a une limite par défaut de 50 sockets en attente. Si vous essayez d'ouvrir une autre connexion, celles-ci seront refusées. La solution consiste simplement à modifier cette configuration par défaut côté serveur. Dans le cas suivant, je crée un serveur Socket qui écoute sur le port TCP 10_000et accepte le maximum de 200sockets en attente.

new Thread(() -> {
      try (ServerSocket serverSocket = new ServerSocket(10_000, 200)) {
        logger.info("Server starts listening on TCP port {}", port);

        while (true) {
          try {
            ClientHandler clientHandler = clientHandlerProvider.getObject(serverSocket.accept(), this);
            executor.execute(clientHandler::start);
          } catch (Exception e) {
            logger.error(e.getMessage());
          }
        }

      } catch (IOException | SecurityException | IllegalArgumentException e) {
        logger.error("Could not open server on TCP port {}. Reason: {}", port, e.getMessage());
      }
    }).start();

Depuis Javadoc de ServerSocket :

La longueur maximale de la file d'attente pour les indications de connexion entrante (une demande de connexion) est définie sur le paramètre backlog. Si une indication de connexion arrive lorsque la file d'attente est pleine, la connexion est refusée.


0

Le problème peut être que vos fichiers déployés ne sont pas mis à jour avec les méthodes RMI appropriées. Vérifiez que votre interface RMI a mis à jour des paramètres ou des structures de données mises à jour que votre client ne possède pas. Ou que votre client RMI n'a aucun paramètre différent de celui de votre version de serveur.

Ceci est juste une supposition éclairée. Après avoir redéployé les fichiers de classe de mon application serveur et retesté, le problème du "tuyau cassé" a disparu.


Quelles méthodes RMI? RMI n'est pas mentionné dans la question.
Marquis of Lorne

0

Les réponses ci-dessus illustrent la raison de ceci java.net.SocketException: Broken pipe: l'autre extrémité a fermé la connexion. J'aimerais partager l'expérience de ce qui s'est passé quand je l'ai rencontré:

  1. dans la demande d'un client, l'en- Content-Typetête est défini par erreur plus grand que le corps de la demande (en fait, il n'y avait pas de corps du tout)
  2. le service du bas dans la socket tomcat attendait cette taille de données de corps (http est sur TCP qui assure la livraison en encapsulant et ...)
  3. à l'expiration de 60 secondes, tomcat lance une exception de délai d'expiration: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception java.net.SocketTimeoutException: null
  4. le client reçoit une réponse avec le code d'état 500 en raison de l'exception de délai d'expiration.
  5. client ferme la connexion (car il reçoit une réponse).
  6. tomcat jette java.net.SocketException: Broken pipeparce que le client l'a fermé.

Parfois, tomcat ne lance pas d'exception de pip cassé, car une exception de délai d'expiration ferme la connexion, pourquoi une telle différence me déroute aussi.


L'en- Content-Typetête ne spécifie pas de nombre, il ne peut donc pas être «plus grand» que tout. Les exceptions de temporisation ne ferment pas le socket. La réponse n'a aucun sens.
Marquis of Lorne

@EJP peut-être que c'était la longueur du contenu, je ne m'en souviens pas. Je pense que le délai d'expiration dans Tomcat le fait retourner 500, tandis que le client reçoit cette réponse et ferme la connexion, ce qui fait finalement que Tomcat ne reçoit pas assez d'octets comme il l'attend.
Tiina

Si Tomcat envoie 500, il a déjà reçu tout ce qu'il prévoit. Cela n'a toujours pas de sens.
Marquis of Lorne

@ user207421 pas vraiment. Tomcat envoie 500 car il ne reçoit pas tout ce qu'il attend, mais a expiré.
Tiina

-1

JavaDoc:

La longueur maximale de la file d'attente pour les indications de connexion entrante (une demande de connexion) est fixée à 50. Si une indication de connexion arrive lorsque la file d'attente est pleine, la connexion est refusée.

Vous devez augmenter le paramètre "backlog" de votre ServerSocket, par exemple

int backlogSize = 50 ;
new ServerSocket(port, backlogSize);

1
L'augmentation du backlog ne résoudra pas une erreur de «tuyau cassé».
Marquis of Lorne

1
mais ça m'a aidé
TheSecond

@EJP J'ai testé un serveur sur Linux et cela a fonctionné, mais sur Mac OS - pas. Et l'augmentation de la taille de l'arriéré m'a aidé. Je ne savais pas que la taille maximale était de 50.
TheSecond

1
Vous avez testé autre chose. L'arriéré d'écoute n'a rien à voir avec cette exception. Cela aide les refus de connexion ou les délais d'expiration chez le client. Pas d'exceptions «socket fermé», qui sont une erreur locale.
Marquis de Lorne

Pour les pools de sockets (comme un serveur HTTP comme Tomcat), il existe des paramètres de configuration pour le nombre de "acceptations" en attente que le serveur "mettra en file d'attente" avant de distribuer les threads de maintenance et un paramètre séparé pour le nombre de threads dans le pool. Cependant, rien de tout cela n'est lié au problème qu'après le serveur "accept ()" lorsque la connexion est établie - le flux de sortie est soudainement (involontairement) "fermé".
Darrell Teague

-1

La cause est que le pair distant ferme sa socket (crash par exemple), donc si vous essayez de lui écrire des données par exemple, vous obtiendrez ceci. pour résoudre cela, protégez les opérations de lecture / écriture sur socket entre (essayez catch), puis gérez le cas de perte de connexion dans le catch (renouveler la connexion, arrêter le programme, ... etc):

 try {
      /* your code */
 } catch (SocketException e) {
      e.printStackTrace();
      /* insert your failure processings here */
 }

3
Cela enregistrera les problèmes, mais ne les résoudra pas. Pour les résoudre , vous devez (a) résoudre les éventuelles erreurs de protocole d'application et (b) fermer les connexions mortes. Pas seulement les exceptions de journaux, dont la plupart sont fatales.
Marquis of Lorne

@EJP: bien sûr, lorsque vous détectez une erreur, vous devez nettoyer votre socket (contexte) dans l'instruction catch. Je ne peux pas deviner ce que le développeur va faire. il est à lui seul de faire la procédure de nettoyage
elhadi dp ıpɐɥ ן ǝ

Il appartient au développeur de corriger le problème de protocole d'application à l'origine de l'erreur, et c'est le cas. Je n'ai rien de votre réponse ou de votre code qui l'aide. faites-le. «Insérer des opérations de lecture / écriture entre» ne résout pas le problème. Votre réponse est incorrecte.
Marquis of Lorne

@ user207421, le développeur demande comment réparer le tuyau cassé. je lui ai indiqué de protéger d'abord son programme en utilisant (try catch). cela évitera au programme de planter. puis j'insère un commentaire pour lui indiquer de faire un traitement propre. Généralement dans une erreur de tuyau cassé, il existe de nombreuses alternatives, je ne peux pas deviner l'objectif du programme, arrêter le programme, renouveler la connexion, les données sont corrompues ou non .... etc. c'est au programmeur de décider quelle option faire? qu'est-ce qui n'allait pas avec ma réponse?
elhadi dp ıpɐɥ ן ǝ
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.