Questions sur les sockets.

Questions sur les sockets. - C - Programmation

Marsh Posté le 31-10-2005 à 09:04:34    

Bonjour !
 
J'essaie en ce moment de bidouiller avec les sockets en C. Tout allait bien jusqu'au moment ou je me suis rendu compte que des fonctions comme connect ou recv bloquent le programme tant qu'elles n'ont pas de résultat satisfaisant. Après quelques recherches, je trouve le moyen de passer en mode non-bloquant avec un ioctl. Mais sur ce moe non-bloquant je n'ai pas trouvé beaucoup de doc alors voilà mes questions :
 
TOUT CELA EN MODE CONNECTE (tcp) !
 
- Comment peut-on gérer un "connect" non-bloquant ? En le répétant n fois jusqu'à un résultat, ou alors n secondes ?
- Pourquoi "select" signale-t-il des données vides qui font bloquer le recv ? (voir code à la fin).
Et une autre question sans rapport avec les sockets bloquants/non-bloquants :
- Comment savoir si un socket et toujours actif ou s'il a été fermé par le serveur
 
Et puis si vous avez des liens de tutos ou de cours sur les sockets je suis prenneur !
 
Merci d'avance !
 
Mon code avec select :

Code :
  1. int main(int argv, char **argc)
  2. {
  3. struct sockaddr_in so_addr;
  4. int fd;
  5. char buffer[1024] = "message d'authentification";
  6. struct timeval tv;
  7. fd_set readfds;
  8. tv.tv_sec = 0;
  9. tv.tv_usec = 0;
  10. so_addr.sin_family = AF_INET;
  11. so_addr.sin_port = htons(atoi(argc[2]));
  12. memcpy(&so_addr.sin_addr, gethostbyname(argc[1])->h_addr, sizeof(u_long));
  13. fd = socket(AF_INET,SOCK_STREAM,0);
  14. connect(fd, (void*)&so_addr, sizeof(struct sockaddr_in));
  15. send(fd,buffer, 1024,0);
  16. while(1)
  17. {
  18.  FD_ZERO(&readfds);
  19.  FD_SET(fd, &readfds);
  20.  c = select(fd+1, &readfds, NULL, NULL, &tv);
  21.  if (c == 1 && FD_ISSET(fd, &readfds))
  22.  {
  23.        recv(fd,bf,1024,0);
  24.                         printf("données recues\n" );
  25.  }
  26. }
  27. return 0;
  28. }


Résultat : j'ai "données recues"  qui s'affiche sans arret !
Si j'enleve le "send" qui envoie le message d'authentification au serveur (message auquel je ne devrais recevoir qu'une seule réponse du serveur), il ne se passe rien (pas de données recues, et c'est logique).

Reply

Marsh Posté le 31-10-2005 à 09:04:34   

Reply

Marsh Posté le 31-10-2005 à 09:39:45    

thoduv a écrit :


Mon code avec select :

Code :
  1. struct timeval tv;
  2. tv.tv_sec = 0;
  3. tv.tv_usec = 0;
  4. while(1)
  5. {
  6.  c = select(fd+1, &readfds, NULL, NULL, &tv);
  7. }


Résultat : j'ai "données recues"  qui s'affiche sans arret !


 
Si tu mets un timeout de 0, c'est normal !
 
Pas de timeout (attente infinie) :

 c = select(fd+1, &readfds, NULL, NULL, NULL);


Timeout de 1 minute :


 struct timeval tv;
 
 tv.tv_sec = 60;
 tv.tv_usec = 0;
 
 while(1)
 {
   
  c = select(fd+1, &readfds, NULL, NULL, &tv);
 }


 
D'autre part,  

              recv(fd,bf,1024,0);


C'est pas une bonne idée, car tu ne sais pas combien d'octets ont réelement été reçus. Ce '1024' est horrible ! Quand à 'bf', on ne sait pas ce que c'est... Attention, il est probable que le 0 de la chaine C ne soit pas transmis. Il faut donc lui garder une place à l'arriver...


   char buffer[1024] = "message d'authentification";
   ssize_t nb_rec;
               nb_rec = recv (fd, buffer, sizeof buffer - 1, 0);
 
               if (nb_rec >0)
               {
                  buffer [nb_rec] = 0;
               }



Message édité par Emmanuel Delahaye le 31-10-2005 à 09:51:20

---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 31-10-2005 à 09:43:24    

Non en fait l'idée c'est de porter sur un système sans OS (sans rentrer dans les détails). Donc à chaque cycle, la fonction contenant select est appelée, avec un timeout de 0, pour savoir s'il y a des données à recevoir.

Reply

Marsh Posté le 31-10-2005 à 09:53:10    

thoduv a écrit :

Non en fait l'idée c'est de porter sur un système sans OS (sans rentrer dans les détails). Donc à chaque cycle, la fonction contenant select est appelée, avec un timeout de 0, pour savoir s'il y a des données à recevoir.


Ok, C'est un peu horrible, mais pourquoi pas. Dans ce cas, relis mon post, il y a la solution.
 


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 31-10-2005 à 09:57:44    

Oui mais si je veux recevoir exactement le bon nombre d'octets, il faut faire un truc du style :
 

Code :
  1. while(recv(fd, buffer++,1,0));


 
Et ca c'est un peu lourd aussi non ?

Reply

Marsh Posté le 31-10-2005 à 10:02:20    

thoduv a écrit :

Oui mais si je veux recevoir exactement le bon nombre d'octets, il faut faire un truc du style :
 

Code :
  1. while(recv(fd, buffer++,1,0));


 
Et ca c'est un peu lourd aussi non ?


 
"Recevoir exactement le bon nombre d'octets", ça n'existe pas. Tu proposes une taille max, et recv() te dis combien il a reçu, c'est tout. Comment peux tu prévoir ce qui va se passer au niveau de l'émission distante, et même au niveau des couches inférieures locales ?  
 
D'autre part, j'avais indiqué '> 0' et non '!= 0'. En effet le cas <0 (-1 en fait) peut exister et il indique une erreur.
 
http://wwwcgi.rdg.ac.uk:8081/cgi-b [...] OCKET/recv


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 31-10-2005 à 10:13:35    

Tiens étrange ca marche maintenant ... En tout cas merci beaucoup, je pense que l'erreur venait du fait que j'envoyais 1024 octets au lieu de strlen(buffer) octets.
 
Et sinon pour ce qui est du connect non-bloquant et de verifier l'existance d'un socket (savoir s'il a été fermé par le serveur) ?

Reply

Marsh Posté le 31-10-2005 à 10:28:24    

thoduv a écrit :

Et sinon pour ce qui est du connect non-bloquant


En principe, ça n'existe pas. Les ioctl(), c'est de la bidouille pas forcément bien documentée et encore moins portable... un select() blocant est la bonne solution.

Citation :

et de verifier l'existance d'un socket (savoir s'il a été fermé par le serveur) ?


Surveiller les codes retour des fonctions. -1 = erreur. errno donne la raison (<errno.h> ), perror() l'affiche...


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 31-10-2005 à 11:37:58    

Ok, concretement je peux faire comment pour faire un connect avec un timeout (qui laisse tomber si au bout de n secondes il s'est pas connecté) ?

Message cité 1 fois
Message édité par thoduv le 31-10-2005 à 11:41:04
Reply

Marsh Posté le 31-10-2005 à 11:41:08    

thoduv a écrit :

Tiens étrange ca marche maintenant ... En tout cas merci beaucoup, je pense que l'erreur venait du fait que j'envoyais 1024 octets au lieu de strlen(buffer) octets.


Cela n'a aucune importance. Que tu envoies 10 ou 1024 octets, si tu en attends 1024 de l'autre coté tu en recevras 10 ou 1024 et ta fonction de lecture (read ou recv) t'indiquera combien t'en as reçu.
Evidemment, si tu envoies 1014 octets inutiles ça ralenti ton appli mais le nb d'octets envoyés n'influe pas sur la réussite ou l'échec...


Message édité par Sve@r le 31-10-2005 à 11:42:24
Reply

Marsh Posté le 31-10-2005 à 11:41:08   

Reply

Marsh Posté le 31-10-2005 à 12:05:13    

thoduv a écrit :

Ok, concretement je peux faire comment pour faire un connect avec un timeout (qui laisse tomber si au bout de n secondes il s'est pas connecté) ?


Tu regles une valeur (déjà indiqué) et tu testes le code retour de select(). Il y en a un qui veut dire 'timeout'.


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 01-11-2005 à 18:22:29    

Bon, ca marche mais ca marche très bizarrement, ca bloque tout ...
 
Voilà le code de la fonction appelée à chaque cycle pour tester et lire sur un socket :
 
(simsn_Socket est une structure qui contient le fd du socket ainsi qu'un buffer pour ce socket)
 

Code :
  1. char *SocketCheckRead(simsn_Socket *s)
  2. {
  3. int i,r;
  4. struct timeval tv;
  5. fd_set readfds;
  6. tv.tv_sec = 0;
  7. tv.tv_usec = 0;
  8. FD_ZERO(&readfds);
  9. FD_SET(s->fd, &readfds);
  10. i = select(s->fd+1, &readfds, NULL, NULL, &tv);
  11. if ((i>0) && (FD_ISSET(s->fd, &readfds)))
  12. {
  13.  r = recv(s->fd, s->Buffer, s->BufferSize, 0);
  14.  s->Buffer[r] = '\0';
  15.  simsn_Log("< %s\n", s->Buffer);
  16.  return s->Buffer;
  17. }
  18. else
  19. {
  20.  return NULL;
  21. }
  22. }


 
Et voilà le main :
 

Code :
  1. int i = 0;
  2. char *buffer;
  3. while(i++<5000000)
  4. {
  5.    buffer = SocketCheckRead(monsocket);
  6.    if(buffer != NULL)
  7.    {
  8.        printf("Recu : %s\n", buffer);
  9.    }
  10. }


 
Ca marche, mais les "Recu : ..." n'apparaissent qu'à la fin des 5000000 cycles ... J'ai l'impression que le fait de lire comme ca bloque tout le reste, mais c'est étrange puisque l'execution est censée être linéaire ...

Reply

Marsh Posté le 01-11-2005 à 18:57:38    

thoduv a écrit :

Bon, ca marche mais ca marche très bizarrement, ca bloque tout ...


Normal, select() est blocant, c'est fait pour. L'intéret, c'est qu'il peut se débloquer pour un tas de raisons (programmables)...
 
Si ça te gène, tu le mets dans un thread et on en parle plus.


Message édité par Emmanuel Delahaye le 01-11-2005 à 20:13:38

---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 01-11-2005 à 19:00:41    

Bloquant ? Comment ca ? Je lui ai mis un timeout de 0 ! Et thread je peux pas, c'est fait pour une machine sans OS.

Reply

Marsh Posté le 01-11-2005 à 19:13:25    

thoduv a écrit :

Bloquant ? Comment ca ? Je lui ai mis un timeout de 0 ! Et thread je peux pas, c'est fait pour une machine sans OS.


Il faut mettre l'init de tv dans la boucle avant le select(), car select() modifie tv. (Lire la doc)
 

Citation :

On Linux, the function select modifies timeout to reflect the amount of time not slept; most other implementations do not do this.  This causes problems both when Linux code which reads timeout is ported to other operating systems, and when code is ported to Linux that reuses a struct timeval for multiple select s in a loop without reinitializing it.  Consider timeout to be undefined after select returns.  


 
Machine sans OS, ça m'inquiète. Quelle machine ? Quel compilateur ? Qu'est-ce qui t'empêche d'utiliser un Linux [embarqué] pour cette machine ?
 
Rappel : select() n'est pas standard, mais POSIX. POSIX est une norme 'système'.


Message édité par Emmanuel Delahaye le 01-11-2005 à 19:15:28

---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 01-11-2005 à 19:15:53    

Je constate l'inverse ...
 
Attente infinie :

Code :
  1. select(fdmax, read, write, exept, NULL);


Attente nulle   :

Code :
  1. timeval tv;
  2. tv.sec=0;
  3. tv.usec=0;
  4. select(fdmax, read, write, exept, &tv);


 
Non ?
 
Edit: Non je ne peux pas utiliser de Linux embarqué, et c'est n'est pas un problème, j'ai des fonctions équivalentes aux fonctions Linux (select, connect, etc ...) sur cette machine.

Message cité 1 fois
Message édité par thoduv le 01-11-2005 à 19:18:19
Reply

Marsh Posté le 01-11-2005 à 19:17:22    

thoduv a écrit :

Je constate l'inverse ...
 
Attente infinie :

Code :
  1. select(fdmax, read, write, exept, NULL);


Attente nulle   :

Code :
  1. timeval tv;
  2. tv.sec=0;
  3. tv.usec=0;
  4. select(fdmax, read, write, exept, &tv);


 
Non ?


Oui, j'ai annulé et reposté...
 


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 01-11-2005 à 19:20:45    

Ok. Mais mon select est dans une fonction, et le timeval est défini dans la fonction également, donc à chaque appel de la fonction tv est remis à 0
 


main
{
 boucle  
 {
  recevoir();
 }
}
 
recevoir
{
 initialise tv
 initialise ensemble select
 select
}

Reply

Marsh Posté le 01-11-2005 à 19:34:05    

thoduv a écrit :

Ok. Mais mon select est dans une fonction, et le timeval est défini dans la fonction également, donc à chaque appel de la fonction tv est remis à 0
 


main
{
 boucle  
 {
  recevoir();
 }
}
 
recevoir
{
 initialise tv
 initialise ensemble select
 select
}



 
Ok. Avec quel système testes-tu ?


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 01-11-2005 à 19:37:02    

Reply

Marsh Posté le 01-11-2005 à 20:19:49    


Ok. En fait qu'est-ce qui ne fonctionne pas exactement ? Le comportement que tu décris parait finalement assez normal. Si tu en doutes, affiche la valeur du compteur.
 
Et ta trace simsn_Log(), elle dit quoi ?
 


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 01-11-2005 à 20:50:48    

Voilà ce que ca fait exactement :
 

- Connection du socket.
- Envoie message authentification.
- Boucle de 5000000 cycles :
| - Fonction de réception (avec select).
| - Affichage des données recues (s'il y en a).


 
Ce qui commence par # est un log.
Ce qui commence par > est un log de message envoyé.
Ce qui commence par < est un log de message recu.
 

# Socket connecté.  
> AUTHENTIFICATION                    (Envoi)
< AUTHENTIFICATION REUSSIE    (Reception)
... Là le programme se bloque (écoulement des 5000000 cycles)
AUTHENTIFICATION REUSSIE        (Affichage du buffer de réception : ca aurait du être fait juste après le log de cette reception)
# Socket deconnecté.


 
Donc le problème c'est que l'affichage du message recu bloque sur le dernier caractère (pas normal du tout).
 
Ce n'est pas select qui bloque, car si on place un log du style log("-" ); dans la fonction de réception juste après le select(), on obtient l'affichage d'un "-" à chaque cycle (donc une masse pas possible dans la console).
 
Je comprends vraiment pas, pourtant l'affichage des données recues dans le log marche, mais il bloque une fois que la fonction de réception s'est terminée en renvoyant l'adresse du buffer ...

Reply

Marsh Posté le 01-11-2005 à 21:08:16    

thoduv a écrit :

Voilà ce que ca fait exactement :
 

- Connection du socket.
- Envoie message authentification.
- Boucle de 5000000 cycles :
| - Fonction de réception (avec select).
| - Affichage des données recues (s'il y en a).


 
Ce qui commence par # est un log.
Ce qui commence par > est un log de message envoyé.
Ce qui commence par < est un log de message recu.
 

# Socket connecté.  
> AUTHENTIFICATION                    (Envoi)
< AUTHENTIFICATION REUSSIE    (Reception)
... Là le programme se bloque (écoulement des 5000000 cycles)
AUTHENTIFICATION REUSSIE        (Affichage du buffer de réception : ca aurait du être fait juste après le log de cette reception)
# Socket deconnecté.


 
Donc le problème c'est que l'affichage du message recu bloque sur le dernier caractère (pas normal du tout).
 
Ce n'est pas select qui bloque, car si on place un log du style log("-" ); dans la fonction de réception juste après le select(), on obtient l'affichage d'un "-" à chaque cycle (donc une masse pas possible dans la console).
 
Je comprends vraiment pas, pourtant l'affichage des données recues dans le log marche, mais il bloque une fois que la fonction de réception s'est terminée en renvoyant l'adresse du buffer ...


 
L'affiche est retardé. C'est ça qui te choque ? Quitte ta boucle dès reception, pour voir...
 
C'est sûr que de faire une attente active, ça bouffe 100% du CPU (Rappel, Linux est un système coopératif, et non préemptif). La tâche d'affichage étant moins prioritaire, elle passe après. L'attente active, c'est mauvais, mais c'est ça que tu veux, alors il faut en subir les conséquences... Essaye quand même de rendre la main au système avec un usleep() de quelques µs dans la boucle... Ton CPU va respirer un peu mieux...


---------------
Des infos sur la programmation et le langage C: http://www.bien-programmer.fr Pas de Wi-Fi à la maison : http://www.cpl-france.org/
Reply

Marsh Posté le 01-11-2005 à 21:14:15    

Ouah merci !
 

Code :
  1. usleep(1);

dans la boucle et ca repart !
 
Pour ce qui est de la consommation CPU c'est pas grave puisqu'au final mon appli aura un CPU pour elle toute seule.

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

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