connect qui échoue de façon aléatoire

connect qui échoue de façon aléatoire - C - Programmation

Marsh Posté le 04-10-2010 à 16:59:54    

Salut,
 
 
Je suis en train de porter une partie de code sous FreeBsd et je suis confronté à un problème que je n'arrive pas à résoudre :(
 
 
En gros , l'algo est assez simple :
 
coté client :
 
je crée une connexion sur un serveur ( TCP sur port 50004 )
si connexion réussie je tente de transférer n fichiers
pour chaque fichier à transférer je fais une demande au serveur ( demande de type question-réponse en XML  ) toujours sur 50004, et si la demande est acceptée, je lance la fonction qui transfère un fichier complet.
Cette fonction se connecte sur un port dédié ( TCP / 50005 ) , transfère les datas  , et ferme le port. Ceci répété pour chaque fichier.
 
Coté serveur :
J'écoute des demandes de connexion sur TCP/50004, dès qu'une demande arrive , je crée un thread qui va gérer ma connexion avec le client.
Dans ce thread, je reçois les demandes ( type question-réponse XML ) , dont une qui me permet de transférer un fichier.
Si une demande de tel type arrive, je crée une nouvelle socket d'écoute sur 50005 , et je récupère ce que m'envoie le client.
 
 
(j'ai simplifié au max).
 
Sous windows et sous linux, cela fonctionne parfaitement bien. Par contre, je dois porter mon programme sous freenas, et le transfert d'un répertoire se fait maintenant de façon aléatoire. En gros, le connect sur le port 50005 échoue de temps en temps avec 2 erreurs sur errno : EINVAL ou EADDRINUSE.
 
Je n'ai pas trouvé de doc assez claire m'expliquant dans quels cas précis j'obtiens ce genre d'erreur.
 
 
Voici mon code coté client ( simplifié ):

Code :
  1. int Init( const char *hote, int port )
  2. {
  3.    
  4.     struct sockaddr_in  adresse;
  5.  
  6.     SOCKET              sock = (SOCKET)-1;
  7.     int                 ret = -1;
  8.     int                 oui = 1;
  9.     memset (&adresse, 0 sizeof(adresse));
  10.     adresse.sin_family = AF_INET;
  11.     adresse.sin_addr.s_addr = INADDR_ANY; 
  12.     adresse.sin_addr.s_addr  = inet_addr(hote);
  13.     adresse.sin_port = htons(port);
  14.     // création du socket
  15.     if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) > 0)
  16.     {
  17.    
  18.         for (int essais = 1;; essais++)
  19.         {
  20.                 pret = (connect(sock, (struct sockaddr*)&adresse, sizeof(adresse)) == 0);
  21.            
  22.                 if (pret)                                   
  23.               break;
  24.        
  25.                 if ( (errno != EADDRINUSE && errno != ECONNREFUSED) || essais >= MAX_ESSAI)     
  26.               break;
  27.         usleep(essais*100000);                               
  28.          } 
  29.          if ( pret )
  30.          {
  31.               ret = 0;
  32.          }
  33.          else
  34.          {
  35.              closesocket(sock);
  36.          }
  37.        
  38.     }
  39.        
  40.     ret = 0;
  41.    
  42.     return ret;
  43. }


 
Le code coté serveur est inutile, le problème n'étant pas coté serveur ( l'accept coté serveur expire au bout de son timeout puisque le connect coté client ne réussit jamais).
 
la fonction connect échoue aléatoirement (errno = EINVAL ou EADDRINUSE ) si je demande de transférer un répertoire d'une centaine de fichier de taille 16 octets environ, et échoue moins si je la ralentis en mettant une temporisation avant le premier connect.
 
Ce même code n'échoue jamais sous linux et sous windows ( la boucle retry est là pour pallier un problème de conception qui fait que le client peut parfois demander un connect avant que le serveur ait créé sa socket d'écoute ). D'ailleurs il est indiqué dans les pages man de connect que le code d'erreur EINVAL n'existe que sous BSD ( sans plus de détails). Edit : erreur de ma part , mal lu la page man. Cela dit, pas trouvé de détail sur EINVAL.
 
Y a-t-il une erreur flagrante que je n'aurais pas vu ? J'ai lu 2-3 trucs sur le net comme quoi ça pourrait venir d'une mauvaise initialisation de la structure adresse, mais si c'était le cas, ça échouerait à chaque fois ?
 
Merci pour vos réponses  :jap:


Message édité par xilebo le 04-10-2010 à 17:14:45
Reply

Marsh Posté le 04-10-2010 à 16:59:54   

Reply

Marsh Posté le 04-10-2010 à 17:16:24    

Au cas où , je ne pense pas à un écrasement mémoire d'aucun des 3 paramètres de la fonction connect, j'ai vérifié chacun des paramètres, ils ont toujours la même valeur (ou le même contenu concernant la structure ). Je ne comprends pas le EINVAL alors qu'entre 2 appels, les variables ont exactement la même valeur :(

Reply

Marsh Posté le 04-10-2010 à 19:29:44    

Ça ne serait pas lié aux connexions TCP dans l'état TIME_WAIT ? Par exemple, la config par défaut de Windows server 2003, c'est d'allouer les ports pour les connexions temporaires seulement entre 1024 et 5000 (http://technet.microsoft.com/en-us [...] 10%29.aspx). Une fois tous les ports alloués, ça risque de péter de tous les cotés. Ça pourrait être le même bordel sur ton système.
 
Fait un "netstat -a -n", ça devrait lister toutes les connexions de la machine. Si tu vois un paquet de TIME_WAIT (genre plusieurs milliers), ça pourrait être ça le problème. Dans ce cas, essaie de spécifier l'option SO_REUSEADDR sur la socket.

Message cité 1 fois
Message édité par tpierron le 04-10-2010 à 19:31:52
Reply

Marsh Posté le 05-10-2010 à 08:54:22    

tpierron a écrit :

Ça ne serait pas lié aux connexions TCP dans l'état TIME_WAIT ? Par exemple, la config par défaut de Windows server 2003, c'est d'allouer les ports pour les connexions temporaires seulement entre 1024 et 5000 (http://technet.microsoft.com/en-us [...] 10%29.aspx). Une fois tous les ports alloués, ça risque de péter de tous les cotés. Ça pourrait être le même bordel sur ton système.
 
Fait un "netstat -a -n", ça devrait lister toutes les connexions de la machine. Si tu vois un paquet de TIME_WAIT (genre plusieurs milliers), ça pourrait être ça le problème. Dans ce cas, essaie de spécifier l'option SO_REUSEADDR sur la socket.


 
 
Je viens de lister les connexions avec netstat , effectivement, chacune des connexions utilisée pour transférer un fichier est en état TIME_WAIT , alors que je la ferme bien avec un closesocket(). Elles disparaissent toutes seules au bout d'un temps que je ne connais pas ( < à la minute ) , mais pas assez rapidement parce que je transfère un gros nombre de fichiers très petit ( donc très rapide).
 
Peut être mon algo de transfert ne convient pas pour ce que je veux faire, à savoir ouvrir et fermer une socket de transfert pour chaque fichier, mais plutot réutiliser la même socket pour tous les fichiers, mais je ne peux pas me permettre de faire une modif importante pour le moment :(
 
Je vais tester l'option que tu me donnes ( SO_REUSEADDR  ) mais je t'avouerai que j'ai des doutes, car je ne l'ai pas mis dans l'exemple ci-dessus, mais l'option est bien présente dans le code :
 

Code :
  1. if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&oui, sizeof(oui)) < 0)
  2. {
  3.     // ...
  4. }

Reply

Marsh Posté le 05-10-2010 à 14:54:53    

Il est aussi possible qu'il y ait un intervalle limité de port allouable. Un test vite fait que je ferais, c'est ouvrir/fermer de connexions en boucle, le plus rapidement et voir au bout de combien de temps ça retourne une erreur, vérifie ensuite avec netstat le nombre de connexion en TIME_WAIT. S'il y en a moins de 64000 (plus ou le moins de le maximum théorique), il doit forcément y avoir un paramètre qui limite l'intervalle d'allocation (dans le cas de windows, il y a le MaxUserPort dans la base de registre, pour les autres systèmes va falloir chercher).
 
Cela dit, 60s pour une socket qui reste dans l'état TIME_WAIT, ce n'est pas énorme (sous Windows, c'est 400s par défaut). Où l'intervalle est très petit sur ton système, où tu ouvres vraiment trop de connexion à la chaine (peut-être dans ce cas utiliser un mécanisme similaire au Keep-Alive + Content-Length HTTP).


Message édité par tpierron le 05-10-2010 à 14:55:38
Reply

Marsh Posté le 07-10-2010 à 16:14:05    

Salut :)
 
Merci pour tes infos. J'ai revu mon algo de transfert de fichiers pour réutiliser une socket deja ouverte d'un fichier à l'autre.
 
Cela dit, je n'ai toujours pas compris pourquoi cela ne fonctionnait pas de manière aléatoire sur la machine freebsd ( freenas ) avec peu de port ouvert finalement ( moins de 20 ). Cela arrivait par contre systématiquement après un fichier petit (donc 2 ouvertures / fermetures  successives et rapides ).
 
 
Merci de ton aide  :jap:

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed