Problème de sockets TCP

Problème de sockets TCP - C - Programmation

Marsh Posté le 20-01-2006 à 09:42:14    

Bonjour à tous !
 
J'ai écrit un petit serveur (sous Linux) permettant la réception et l'envoi de messages sur le réseau. Cependant, lors de l'envoi de plusieurs messages d'affilé à un même client, tous les messages sont placés dans le même paquet TCP au lieu d'être placé dans des paquets différents. Ce problème ne se produit pas si je fais tourner le client et le serveur sur la même machine via l'interface de loopback ou si je mets une temporisation d'une seconde entre chaque messages.
 
J'ai écrit une fonction send_to permettant d'envoyer un message à un client et qui est appelée par les couches supérieures de l'application et une fonction wait_client, lancée dans un thread, qui est chargée d'initialiser la connexion avec un nouveau client et de stocker les messages entrant dans une file de réception.
Je gère la liste des clients via une liste chainée (liste_client_t est une structure correspondant à un maillon de cette liste)
 
A tout hasard, voici le code de la fonction d'envoi des messages et celui des fonctions de réception

Code :
  1. void send_to(int sock,char *message){
  2. pthread_mutex_lock(&mutex_sock);
  3. if(send(sock,message,strlen(message),0) <1){
  4.  fprintf(stderr,"ERR : %s,%i : Envoi d'un message à %i (%s)\n",__FILE__,__LINE__,sock,message);
  5.  perror("send" );
  6.  pthread_mutex_unlock(&mutex_sock);
  7.  return;
  8. }
  9. sleep(1);
  10. pthread_mutex_unlock(&mutex_sock);
  11. }


 

Code :
  1. static int sock_server  // socket du serveur, déclarée en global
  2. static int read_data(liste_clients_t *c){
  3. int numbytes=0;   // Nombre d'octets reçus
  4. char buf[BUFFERSIZE];  // Buffer de réception des messages
  5. if((numbytes=recv(c->c->socket, buf, BUFFERSIZE, 0)) <= 0) {
  6.  switch(numbytes){
  7.   case 0 :
  8.    fprintf(stderr,"ERR : %s:%i : Le client a coupé la connexion\n",__FILE__,__LINE__);
  9.    perror("recv" );
  10.   break;
  11.   default :
  12.    fprintf(stderr,"ERR : %s:%i : Erreur lors de la réception d'un message (erreur : %i)\n",__FILE__,__LINE__,numbytes);
  13.    perror("recv" );
  14.  }
  15.  return FALSE;
  16. }
  17. else{
  18.  buf[numbytes] = '\0';
  19.  pthread_mutex_lock(&mutex_file);
  20. #ifdef DEBUG
  21.  fprintf(stderr,"DBG : %s:%i : Client : %i, %s : %s\n",__FILE__,__LINE__,c->c->socket,inet_ntoa(c->c->addr_client.sin_addr),buf);
  22. #endif
  23.                // Ajout du message reçu dans la file d
  24.  if(!add_message(buf,c->c->socket)){
  25.   fprintf(stderr,"ERR : %s:%i : buffer d'entrée plein\n",__FILE__,__LINE__);
  26.  }
  27.  pthread_mutex_unlock(&mutex_file);
  28. }
  29. return TRUE;
  30. }
  31. /**
  32. * Thread permettant de placer le serveur en attente de connexion
  33. * */
  34. static void *wait_client(){
  35. int max_sock=0;
  36. struct timeval wait_time;
  37. int ret_select;
  38. liste_clients_t *parcours,*temp;
  39. // Liste des sockets pour select();
  40. fd_set list_sock;
  41. while(1){
  42.  wait_time.tv_sec = 0;
  43.  wait_time.tv_usec = 200;
  44.  // On bloque l'accès aux sockets
  45.  pthread_mutex_lock(&mutex_sock);
  46.  // Génération du fd_set pour select
  47.  build_select_list(&max_sock,&list_sock);
  48.  // On attends qu'un message arrive,
  49.  // select modifie list_sock en enlevant les sockets pour lesquelles il ne s'est rien passé
  50.  ret_select = select(FD_SETSIZE,&list_sock,NULL,NULL,&wait_time);
  51.  if(ret_select < 0){
  52.   perror("select" );
  53.   exit(EXIT_FAILURE);
  54.  }
  55.  if(FD_ISSET(sock_server,&list_sock)){
  56.   #ifdef DEBUG
  57.    fprintf(stderr,"DBG : %s:%i : Nouvelle connexion\n",__FILE__,__LINE__);
  58.   #endif
  59.                         // On ajout le client à la liste des clients  
  60.   new_connexion();
  61.   FD_CLR(sock_server,&list_sock);
  62.  }
  63.  else{
  64.   parcours = liste_clients;
  65.   while(parcours != NULL){
  66.    if(FD_ISSET(parcours->c->socket,&list_sock)){
  67.     #ifdef DEBUG
  68.      fprintf(stderr,"DBG : %s:%i : Message arrivé\n",__FILE__,__LINE__);
  69.     #endif
  70.     if(read_data(parcours)){
  71.      FD_CLR(parcours->c->socket,&list_sock);
  72.      parcours = parcours->next;
  73.     }
  74.     else{
  75.      temp = parcours;
  76.      parcours = parcours->next;
  77.      drop_client(temp);
  78.     }
  79.    }
  80.    else{
  81.     parcours = parcours->next;
  82.    }
  83.   }
  84.  }
  85.  pthread_mutex_unlock(&mutex_sock);
  86. }
  87. pthread_exit(NULL);
  88. }


 
Voilà, je remercie d'avance ceux qui m'aideront à régler ce problème. Y a-t'il un appel système du même genre que fflush mais fonctionnant sur les sockets ?

Reply

Marsh Posté le 20-01-2006 à 09:42:14   

Reply

Marsh Posté le 20-01-2006 à 10:25:25    

Le fait d'avoir les messages dans les memes paquets c'est normal, ca fait partie du Nagle Algo (qui attend environ 200 ms avant d'envoyer ou cas ou d'autre donnees sont mise en queue pour le meme destinataire, cela evite les "petits paquets" et les collisions reseaux). Sur la meme machine en loopback, rien ne sort sur le reseau alors ca marche.
Tu peux "disabler" le Nagle, ou alors avant d'envoyer un autre message attendre un reponse du client.

Reply

Marsh Posté le 20-01-2006 à 10:55:07    

J'ai ajouté ça dans mon code pour le désactiver sur la socket du serveur et sur chaque socket des clients :

Code :
  1. setsockopt(socket, IPPROTO_TCP, TCP_NODELAY,&disable_nagle, sizeof (disable_nagle));


 
avec disable_nagle = 1
 
Mais, ça ne fonctionne toujours pas.... :??:
 
Voici la fonction qui est lancée pour traiter les nouvelles connexions

Code :
  1. static void new_connexion(){
  2. int disable_nagle = 1;
  3. // Stockage temporaire du numéro de socket d'un client
  4. int socket_temp;
  5. // Stockage temporaire de l'adresse IP du client
  6. struct sockaddr_in addr_temp;
  7. // Pour passer à la fonction accept()
  8. int sin_size=sizeof(struct sockaddr_in);
  9. if((socket_temp = accept(sock_server, (struct sockaddr *)&addr_temp,&sin_size)) < 0){
  10.  perror("accept" );
  11.  return;
  12. }
  13. client_t *temp = new_client(socket_temp,addr_temp);
  14. add_new_client(temp); // Ajout du nouveau client dans la liste
  15. setsockopt(socket_temp, IPPROTO_TCP, TCP_NODELAY,&disable_nagle, sizeof (disable_nagle));
  16. printf("Nouveau client (%i) : %s\n",temp->socket,inet_ntoa(temp->addr_client.sin_addr));
  17. }


Message édité par Dumbledore le 20-01-2006 à 11:11:21
Reply

Marsh Posté le 20-01-2006 à 11:25:43    

Une autre solution, serait de d'envoyer la taille du message avant le message. Le client pourrait ainsi "couper" au bon endroit. Tu envois un int avec la length, dans le client tu lis d'abord sizeof(int), ensuite le nombre de caracteres. Attention si tu "mixes" les plateform a l'Endian case.

Reply

Marsh Posté le 20-01-2006 à 11:26:13    

J'y avais pensé, mais techniquement, je ne peux pas mettre ta solution en oeuvre pour des raisons de compatibilité avec les clients....
 
En fait, ce qui se passe, c'est qu'il envoie un certain nombre de paquets correctement, puis ensuite, il les envoie n'importe comment alors que les données ont toutes la même taille


Message édité par Dumbledore le 20-01-2006 à 11:27:43
Reply

Marsh Posté le 20-01-2006 à 11:33:29    

bin s'ils font tous la meme taille tu les découpent toi meme alors
 
il faut voir le tcp comme un flux, pas comme des paquets. c'est à toi de découper à l'arrivée.


---------------
-( BlackGoddess )-
Reply

Marsh Posté le 20-01-2006 à 11:42:40    

Ce que je veux dire, c'est que là, j'envoie d'affilé plusieurs messages de même taille lors de l'initialisation de la connexion avec les clients, mais après, en fonctionnement normal, les messages sont de longueur variable.
 
Y a un truc auquel je pensais : vu que tous nos messages sont divisés en champs dont on connait le nombre, ça serait de rajouter un nouveau champ ne contenant que du bourrage. Dans ce cas, quelle est la taille maximale de la charge d'un paquet TCP sur un réseau ethernet ? Les clients pourront faire le tri sans problème , mais le découpage, c'est pas viable
 
Exemple de ce que j'envoie et reçois (c'est pour un jeu et entre les différents groupes, tout doit être compatible):
 

MAP_START
MAP_SIZE:11
MAP_LINE:A A 0 0 0 2 2 0 3 B B
MAP_LINE:A A 0 0 0 0 0 0 0 B B
MAP_LINE:0 0 0 0 0 1 0 0 0 0 1
MAP_LINE:0 0 0 3 3 1 1 0 0 0 0
MAP_LINE:2 2 0 0 0 0 0 0 2 2 0
MAP_LINE:2 3 0 0 0 0 0 0 0 0 0
MAP_LINE:3 3 0 0 0 0 2 2 2 2 0
MAP_LINE:3 0 0 0 0 0 2 2 2 0 0
MAP_LINE:0 0 0 0 0 0 0 0 0 0 0
MAP_LINE:C C 0 0 0 0 0 0 0 D D
MAP_LINE:C C 0 0 0 0 0 0 0 D D
MAP_END


 
Tout ce qui se trouve entre 2 "recu : " correspond à un paquet TCP...

Recu : MAP_START
Recu : MAP_SIZE:11
MAP_LINE:A A 0 0 0 2 2 0 3 B B
MAP_LINE:A A 0 0 0 0 0 0 0 B B
MAP_LINE:0 0 0 0 0 1 0 0 0
Recu :  0 1
MAP_LINE:0 0 0 3 3 1 1 0 0 0 0
MAP_LINE:2 2 0 0 0 0 0 0 2 2 0
MAP_LINE:2 3 0 0 0 0 0 0 0 0 0
MA
Recu : P_LINE:3 3 0 0 0 0 2 2 2 2 0
MAP_LINE:3 0 0 0 0 0 2 2 2 0 0
MAP_LINE:0 0 0 0 0 0 0 0 0 0 0
MAP_LINE:
Recu : C C 0 0 0 0 0 0 0 D D
MAP_LINE:C C 0 0 0 0 0 0 0 D D
MAP_END


Message édité par Dumbledore le 20-01-2006 à 11:50:20
Reply

Marsh Posté le 20-01-2006 à 11:52:27    

La taille des paquets n'est pas toujours la meme (MTU / MSS). De plus dans le cas de WAN etc il peut y avoir des "points" (routeurs ...) qui "redecoupent..donc pas viable.
Par contre comme je disais avant, en envoyant la longeur avant le message normalement t'as pas de probleme, tu peux meme ajouter un calcul de crc pour verifier si tu veux.
 
Ce genre de schema  simplifie
 
cote serveur :
 
int SndMyMessage(socket s, const char* mess)
{
     int i = strlen(mess);
 
     snd(s, &i, sizeof(int));
     snd(s, mess, i);
}
 
cote client :
 
int RcvMyMess(socket s)
{
    int i;
    char buff[256];
 
    rcv(s, &i, sizeof(int));
    rcv(s, buff, i);
}

Reply

Marsh Posté le 20-01-2006 à 21:36:21    

Ouép, mais je peux pas modifier le code des clients...

Reply

Marsh Posté le 26-01-2006 à 19:09:00    

Désolé de faire un UP, mais personne n'a d'idée pour mon problème ? Je ne peux vraiment pas toucher au code des clients pour des raisons d'interropérabilité...
 
Le programme marche impec sur le loopback ou si le réseau n'est pas saturé. Le problème, c'est que ce ne sont pas des situations réelles de fonctionnement...

Reply

Marsh Posté le 26-01-2006 à 19:09:00   

Reply

Marsh Posté le 27-01-2006 à 13:49:19    

A vrai dire, si tu ne peux modifier les clients, ça va être dure. Les clients sont donc mal programmé depuis le début.  
 
De plus la solution d'envoyer un int avant n'est absolument pas portable. Car sur certain réseau, le bit de poid fort est mis avant et parfois après. Ce genre de converssion est vraiment lourde au niveau de la programmation.  
C'est vrai que cette solution est à mon avis la meilleure. Mais faut pas passer par un entier, mais par une chaine de caractère de longueur fixe. Les chaines de caractère ne sont pas soumis à ce problème de bit de poids fort/faible.  
 
Voila moi perso je ne vois pas de solution, pour moi c'est donc un problème au niveau de tes clients. C'est à eux à découper les données correctement.  
Si comme tu dis, les longueurs sont variables, les clients doivent bien découper quelque part le paquet reçu. Il faut qu'il le sache. Donc à mon avis, le problème qui se pose ici (qui n'en est pas un à mon avis) tu ne saurais le régler sans aller modifier le client qui va avec...

Reply

Marsh Posté le 27-01-2006 à 14:51:26    

Un int avant est une idee, pour la portabilite rien n'empeche d'envoyer sous format string de 4 bytes ("0124" ) et de faire un receive puis un atoi de cette chaine..
Mais bon si tu ne peux pas modifier les clients ... Peut etre mettre une tempo cote server, c'est a dire que tu attends par exemple 500 ms entre les messages pour le meme client. Dans ton server tu stockes l'heure d'evoie du dernier message et quand tu envois un nouveau tu verifies que l'ecart est bien "suffisant"..
Si les chaines que tu envois sont "null terminated", normalement le client detecte que le nombre de bytes recu est > strlen(chaine), donc il doit savoir qu'il y a une autre chaine... Si c'est des vrais "string" que tu envois, sois sur d'envoyer le '\0' final.
 
C'est un peu de bricolage, c'est vrai qu'un petit changement du soft client serait plus facile.
 

Reply

Marsh Posté le 27-01-2006 à 14:58:36    

Desole, en regardant le code que tu as poste avant .. Apparemment tu recois un maximum de BUFFERSIZE dans le client. La solution c'est que le server envois toujours BUFFERSIZE en longeur (tu completes la fin avec des '\0').. Je ne vois que ca sans modifier le client.

Reply

Marsh Posté le 27-01-2006 à 17:36:03    

j'vais voir ce que ça donne en bourrant avec des \0

Reply

Sujets relatifs:

Leave a Replay

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