Je viens de modifier mon code : il compile... Mais ne marche pas encore
Je dois me préparer pour le boulot...
J'ai failli sortir l'expression "j'ai d'autres chats à fouetter"
Je viens de modifier mon code : il compile... Mais ne marche pas encore
Je dois me préparer pour le boulot...
J'ai failli sortir l'expression "j'ai d'autres chats à fouetter"
En l'état ça marche... Ou presque !
En effet au démarrage du serveur, j'ai :Après, lors de l'ouverture d'un client, j'ai bien :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2./a.out serveur absent : le programme devient serveur: No such file or directory
Après, la fermeture du serveur n'est pas repérée par les clients. Le programme se ferme quand j'envoie un message au serveur mort :
Code : Sélectionner tout - Visualiser dans une fenêtre à part Connexion entrante ... affectée au slot 0.
Alors que j'aurais aimé avoir mon message affiché par :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 $ ./a.out 30 : numéro 0 a rejoint le canal. Message à envoyer au serveur (fin pour arrêter le serveur) : fin 2 : 97 Vérification OK Continuer ? (o/n) : o Message à envoyer au serveur (fin pour arrêter le serveur) : rtfdx [troumad@localhost][~/Documents/socket]
Code : Sélectionner tout - Visualiser dans une fenêtre à part printf("Le serveur a fermé la connexion\n");
Je viens de comprendre pourquoi le "No such file or directory" : je tente une écoute sans savoir s'il y a un serveur. Comme il n'y en a pas, il n'y a pas le fichier SERVER_PATH => c'est un affichage d'avertissement que je gère en interne en lançant le serveur.
Mainetant, la suite...
C'est à ça que sert perror() : cette fonction affiche le message que tu lui passes en argument suivi d'un deux-point et d'un espace, puis du message correspondant à l'erreur dont le code est dans errno.
Donc en général, on écrit dans perror() ce que l'on est (ou était) en train de faire au moment où l'on appelle la fonction, et on la laisse donner d'elle même la raison de l'échec. C'est le principe.
J'avais oublié cette fonction que je n'utilise pas souvent... En fait, ce n'est pas une erreur pour mon programme ! Ça détecte juste l'absence de serveur.
Maintetant, j'ai trouver ce qui ne va pas, mais je ne comprends pas pourquoi ça réagit comme ça ici. C'estqui plante : le programme sort sur cette ligne quand il n'y a pas de serveur à l'écoute, comme si j'avais mis return. Je ne passe même pas par la détection d'erreur qui arrive juste après ! Pourquoi ?
Code : Sélectionner tout - Visualiser dans une fenêtre à part rc = send(sd, buffer, strlen(buffer), 0);
Je comprends ça et mon programme est bon pour partir en exemple non ?
Ça fonctionne chez moi. Tu utilises quel système (et quel noyau) ?
Enlève quand même les fflush(stdin) !Je comprends ça et mon programme est bon pour partir en exemple non ?
Ça, pour le coup, ça me surprend beaucoup. J'ai déjà rencontré ça dans des bibliothèques tierces (Sybase, par exemple), et ça m'avait mis de mauvaise humeur, mais un exit() carrément dans le noyau, c'est sauvage quand même. Sur quel système as-tu déjà rencontré cela ?
Pardon ce n'est pas un exit mais un crash
HPUX et linux (RedHat en tous cas jusqu'à 6).
Plus testé après car mis les poll avant chaque écriture/lecture..
Mais un tour du côté du code source de la stdlib donnera les précisions, ou un essai bête et méchant comme Troumad l'a fait..
Faire un write ou un read alors que le client en face s'est cassé la gueule ou n'est plus là entraîne un crash..
Pour les send/receive je ne sais pas mais je suppose même chose d'après ce qu'il dit...
D'où mes multiples avertissements dans la section réseaux sur le fait que il y a très peu de serveurs/clients mettant en place une gestion propre réellement des pertes de connexions (que ce soit dans la détection des signaux ou pour les écritures), et mon insistance sur l'utilisation des polls, que ce soit afin de vérifier le fait que c'est toujours UP avant de communiquer ou que ce soit pour éviter les crashs)..
Faire un serveur/client 100% fiable est assez long, mais si on se fait une petite biblothèque de gestion générale c'est assez simple, car une fois fait ça s'applique partout..
NOTE : si le client (ou le serveur) crashe via un SEGFAULT par exemple, ça ne génère pas de signaux cross-noeuds, et donc pas de changement d'état du socket.. Cela peut être aussi le cas en intra-plateforme... Un SIGHUP ou autre ne sera donc pas généré et donc pas détectable...
D'accord. Je n'avais pas compris qu'il se connectait d'abord puis tuait le serveur entre temps. Dans ce cas-là, ce n'est pas un crash au sens accidentel du terme mais un SIGPIPE qu'il reçoit. Cela se confirme en lançant le client avec un débogueur, même sans les symboles.
Dans ce cas, c'est normal que le programme prenne fin sans autre forme de notification si celui-ci ne prend pas explicitement en charge le cas de figure. C'est à cela que sert le signal en question…
Non, effectivement, mais c'est normal : le signal émis ne concerne que le processus destinataire et n'a de sens que pour lui. Cela dit, la mort du processus concerné devrait provoquer la fermeture de ses sockets par le système et, de là, provoquer des SIGPIPE chez les processus qui y étaient toujours reliés.NOTE : si le client (ou le serveur) crashe via un SEGFAULT par exemple, ça ne génère pas de signaux cross-noeuds, et donc pas de changement d'état du socket.. Cela peut être aussi le cas en intra-plateforme... Un SIGHUP ou autre ne sera donc pas généré et donc pas détectable...
Ce qui est surprenant, c'est que cela ne soit pas immédiat, même avec un socket de type SOCK_STREAM. Cela dit, un sock_stream est littéralement un socket orienté flux. Cela n'implique pas forcément qu'il s'agisse d'une liaison orientée connexion.
Ce n'est même pas une question de vider « proprement » ou pas le buffer d'entrée. C'est simplement que ça ne marchera pas du tout.
On rappelle qu'il faut comprendre « flush » dans le sens de « tirer la chasse ». Ça veut dire que fflush sert à provoquer l'envoi immédiat des données en attente dans un buffer vers leur destination programmée. Donc, dans le cas de stdout, tout ce qui est en attente est envoyé vers la sortie, par défaut vers l'écran.
Dans le cas de stdin, la destination en question, c'est le processus. Si celui-ci fait fflush(stdin), il provoque l'envoi immédiat des données en attente… vers lui-même ! Donc, s'il ne les lit pas, il n'y a aucune raison pour que ça bouge.
D'une certaine manière, le fait que le buffer de stdout se retrouve vide après l'opération peut presque être considéré comme un effet de bord. fflush() sert donc à vider un buffer de sortie comme on vide un stock, mais certainement pas à l'effacer.
La manière la plus propre, je pense, consiste à passer temporairement le descripteur de fichier en mode non-bloquant avec fcntl(), puis à aller remplir un buffer d'une taille arbitraire avec fread() et continuer tant que la lecture remplit entièrement le buffer. Alors, on réinitialise les flags d'erreurs de stdin avec clearerr() et on rétablit les attributs d'états initiaux avec fcntl().
Ça veut aussi dire qu'il n'y a pas de moyen 100 % langage C de le faire à coup sûr. Il faut passer par la programmation système au moins pour passer en mode non bloquant. Au passage, je pense que cette entrée de la F.A.Q. est fausse, car la routine en question s'arrête soit au premier retour à la ligne et il peut y avoir des données après, soit sur un EOF qui n'arrivera jamais si l'entrée standard est une console.
C'est peut-être un autre client qui l'a tué !
Je fais quoi alors pour vérifier si le serveur est mort ? Le serveur envoie un message à tout ces clients pour dire qu'il est mort ?
Je suis assez intéressé par ce point qui n'a rien à voir avec le sujet traité ici
Certes, là, je récupère toute la chaîne entrée sans me poser des questions sur le formatage.
Pour la récupération du o/n (fin du client), j'avais déjà une boucle pour vider le buffer jusqu'au signal "entrée".
nb : je ne peux plus modifier mon code source car ça fait trop longtemps qu'il a été posté. Il suffit d'enlever les fflush(stdin); et tout est bon.
C'est surtout que son contexte d'utilisation n'est pas explicité.Au passage, je pense que cette entrée de la F.A.Q. est fausse, car la routine en question s'arrête soit au premier retour à la ligne et il peut y avoir des données après, soit sur un EOF qui n'arrivera jamais si l'entrée standard est une console.
Un EOF peut intervenir sur une console (encore faut-il le faire exprès) ou plus généralement sur stdin qui peut avoir été redirigé (freopen()).
La fonction peut également facilement être adaptée sur un flux d'entrée (texte) autre que stdin.
J'ai trouvé la réponse sur :http://www.developpez.net/forums/d54...e-retour-send/
Je rajoute un paramètre lors de l'envoi du message à la fonction send :Voici ce qui se passe chez le client :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 rc = send(sd, buffer, strlen(buffer), MSG_NOSIGNAL); /* envoie du message au serveur */ /* MSG_NOSIGNAL pour que l'envoie ne plante pas si le serveur est mort */Est-ce bon selon vous ?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 $ ./a.out 30 : numéro 0 a rejoint le canal. Message à envoyer au serveur (fin pour arrêter le serveur) : fin 2 : 97 Vérification OK Continuer ? (o/n) : o Message à envoyer au serveur (fin pour arrêter le serveur) : aze echec de send(): Broken pipe
Dans ce cas mon exemple est clos !
Si le serveur a réellement l'intention de partir (ou s'il a le temps de se voir mourir :-) ), oui, ce serait une délicate attention de prévenir tous les clients en leur envoyant un message. L'usage veut également que l'on éteigne explicitement le socket associé avec shutdown() avant de le refermer avec close().
Autrement, tu peux mettre un handler de signal sur SIGPIPE. C'est probablement ce que font tous les clients IRC, mais cela nécessite d'apporter une attention particulière à la cause de ce SIGPIPE, qui peut être dû à n'importe quoi.
Autrement, le mieux est de conserver le fonctionnement normal du système. Par exemple, les applications graphiques sous UNIX utilisant X-Window sont en fait des clients se connectant au serveur X. La chute dudit serveur provoque automatiquement l'envoi d'un signal vers toutes les applications qui y sont connectées, et donc leur re-fermeture automatique. Et cela, même si ni le serveur ni les applications clientes ne sont prévues pour gérer ce cas de figure (en fait, la X-lib reconnaît ce cas, bien sûr, mais se contente de faire le ménage).
C'est aussi pour cela que ton client, comme le serveur auquel il se connecte, devrait gérer ses descripteurs avec poll() ou select(). L'idée est toujours de se mettre à l'écoute de n descripteurs — qui peuvent être des sockets ou des descripteurs ordinaires — et de se débloquer dès que l'un d'eux à quelque chose à dire. Ça peut être le socket quand le serveur envoie un message comme l'entrée standard quand c'est l'utilisateur qui saisit une commande.
Le plus simple dans ce cas, c'est scanf(), alors.Je suis assez intéressé par ce point qui n'a rien à voir avec le sujet traité ici Certes, là, je récupère toute la chaîne entrée sans me poser des questions sur le formatage. Pour la récupération du o/n (fin du client), j'avais déjà une boucle pour vider le buffer jusqu'au signal "entrée".
Mais la plupart du temps, il vaut mieux aller au plus simple et laisser le système adopter son comportement par défaut à moins d'avoir une raison vraiment justifiée de procéder ainsi. Par exemple, l'utilisateur de ton programme peut avoir pipé la sortie d'un script à ton programme pour le télécommander. Dans ce cas, le vidage clavier explicite risque d'être un handicap.
En plus, si tu voulais être parfaitement irréprochable sur ce point, il faudrait que tu vérifies que ce que tu vides est bien le buffer d'un clavier. Autrement dit, que tu t'assures que ton processus est bien relié à une console.
Ok, mais si tu veux que ton code serve d'exemple, il faudra encore nettoyer quelques points.nb : je ne peux plus modifier mon code source car ça fait trop longtemps qu'il a été posté. Il suffit d'enlever les fflush(stdin); et tout est bon.
Certes, mais la F.A.Q. précise bien « comment vider le buffer clavier ? ». Cela m'ennuie un peu parce que même dans le cas où l'on sait que l'on est relié à une console, la routine n'est pas fiable. Si j'insère une temporisation de dix secondes pour laisser le temps à l'utilisateur de rentrer du texte, que j'appelle la fonction et que j'affiche ce qui suit, avec le code suivant :
Code C : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 #include <stdio.h> #include <unistd.h> void clean_stdin(void); int main (void) { int c; sleep (10); clean_stdin(); setbuf (stdout,NULL); /* Affichage immédiat sur la SORTIE standard */ puts ("Lecture du tampon de l'entrée standard :"); while ((c=fgetc(stdin))!=EOF) putchar (c); return 0; }
… et que je saisis le code suivant dans l'intervalle :
Code Shell : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 $ ./programme Abcde<Return> Fghij<Return> Klmno<Ctrl-D>
(on remarque l'absence de « Return » avant le Ctrl-D final), j'obtiens :
Code Shell : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 Lecture du tampon de l'entrée standard : Fghij Klmno
Donc le buffer n'est pas vide après l'appel à la fonction. À l'inverse, et c'est là le plus gênant à mon avis, si le buffer est réellement vide à l'appel de la fonction (cas le plus probable) ou s'il ne l'est pas, mais que j'ai terminé la saisie par Ctrl-D sans insérer de retour à la ligne, alors la fonction de nettoyage va rester bloquée en lecture jusqu'à ce que l'utilisateur se décide à entrer un retour à la ligne. En tout état de cause, EOF ne permet pas de savoir si le buffer d'entrée d'un processus relié à une console est vide…
Voici ce que je propose à la place, sous systèmes compatibles POSIX :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 #ifndef _POSIX_SOURCE #define _POSIX_SOURCE #endif #include <errno.h> #include <stdio.h> #include <fcntl.h> /* Vide le buffer d'entrée d'un flux utilisable * en lecture. Renvoie 1 en cas d'échec, 0 sinon. */ int fclearbuf (FILE * f) { int ret = 1; int fd = -1; if (f!=NULL) if ((fd=fileno(f))>=0) { long flags = 0; flags = (long)fcntl (fd,F_GETFL,0); if (!(flags & O_WRONLY)) { char buffer [64]; fcntl (fd,F_SETFL,flags | O_NONBLOCK); while (fread(buffer,sizeof buffer,1,f)==1); if (ferror(f) && errno == EWOULDBLOCK) { clearerr (f); ret = 0; } fcntl (fd,F_SETFL,flags); } } return ret; }
J'ai donc rajouté ceci lors de la fermeture du serveur :As-tu vu ma remarque sur MSG_NOSIGNAL ? Selon moi, elle gère correctement la mort du serveur.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 shutdown(sd,SHUT_RDWR); close(sd); remove(SERVER_PATH);
Après, tu fais une remarque sur le scanf, c'est surtout son utilisation qui me fait utiliser les fflush(stdin). En effet, si tu fait un scanf("%d",&i) et que l'utilisatur rentre 12.456fdsf, seul le 12 sera lu et il restera .456fdsf dans le buffer ! Tu gères ce cas comment ? Sans parler des mélanges de scanf et getchar qui sont catastrophiques ! Peut-être avec ta fonction int fclearbuf (FILE * f) *
Finalement, scanf est complètement déconseillé !
Quels seraient les point à modifier pour faire de ce programme un exemple simple ? Ne pas perdre l'idée qu'un exemple soit simple... C'est à dire que si l'exemple ne porte pas sur la récupération des entrées claviers, unscanf peut-être acceptable si on met un avertissement sur l'utilisation su scanf en début d'exemple.
* : dans ce cas, on sort du cas simple souhaité ici.
@Obsidian
Ce que tu dis est exact et le titre de la FAQ est mal choisi : ce n'est effectivement pas une fonction qui vide le buffer d'entrée dans un cadre général.
En fait, historiquement, cette fonction a été mise dans la FAQ simplement pour répondre aux problèmes fréquents posés par l'utilisation de scanf()consécutifs, du genre
scanf("%d",...);
scanf("%c",...);
avec l'inconvénient évident qu'il faut que le buffer ne soit pas vide lors de l'appel à la fonction.
Elle est simple et utile pour cet usage, mais il faudrait préciser le contexte d'utilisation de cette fonction (et adapter son titre).
J'ai juste remarqué (le système client/serveur sur lequel je travaillais était "critique", c'est à dire que opérationnellement il était vital (au vrai sens) de garantir que rien ne crashe jamais et que l'on relance automatiquement les serveurs/clients si l'un se plantait tout en gardant les requêtes en cours), que le moyen le plus "catch all" était d'avoir simultanément les signal handlers ET les polls.
Les fonctions utilisées par poll sont les seules à vérifier l'état d'un socket SANS en modifier l'état...
Une utilisation telle que :
Code C : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 /* * C h e c k s _ C o m m u n i c a t i o n * */ static int Checks_Communication ( int Canal_In, int Canal_Out ) { int s, ncar_a_lire, ss ; struct pollfd fds[2] ; fds[0].events = POLLOUT ; fds[1].events = POLLIN ; /* --- Assigns the values for the server to be checked */ fds[0].fd = Canal_Out ; fds[1].fd = Canal_In ; /* --- Checks the state of the connection */ s = poll (fds, 2, 200 ); /* --- Either if failed (busy, frozen, error, or client hangup) */ if ( (s <= 0) || (fds[0].revents & POLLERR) || (fds[1].revents & POLLERR) ) { if ( s <= 0 ) s = INFO ; else s = WARNING ; } else if ( (fds[1].revents & POLLHUP)|| (fds[0].revents & POLLHUP) || (fds[1].revents & POLLNVAL)|| (fds[0].revents & POLLNVAL) ) s = ERROR ; else { /* Otherwise it is ok to try and write on it */ ss = ioctl(fds[1].fd, FIONREAD, &ncar_a_lire); if ( (ncar_a_lire == 0) && (ss >= 0) && (s == 2) && (fds[1].revents & POLLIN) ) s = ERROR ; else s = SUCCESS ; } return (s); } /* * S e c u r e _ W r i t e _ C o m m u n i c a t i o n * */ static int Secure_Write_Communication ( int Canal_In, int Canal_Out, char *command, int Longu ) { int s, i ; time_t now ; if ( (i = Checks_Communication (Canal_In, Canal_Out)) == SUCCESS ) { s = write ( Canal_Out, command, Longu) ; .... } return (s); }
donnait, couplé avec un signal handler qui termine proprement en cas de SIGKILL, SIGTERM, SIGQUIT, SIGPIPE, SIGSTOP, un résultat parfait...
Oui, désolé. J'ai mis une heure à poster mon message et tu avais déposé le tien entre temps.
Je parlais surtout du cas où tu voulais lire un caractère (« o » ou « n ») puis abandonner le reste de la ligne jusqu'au retour. Tu peux faire cela en une opération avec scanf() sans implémenter toi-même la boucle.Après, tu fais un remarque sur le scanf, c'est surtout son utilisation qui me fait utiliser les fflush(stdin). En effet, si tu fait un scanf("%d",&i) et que l'utilisatur rentre 12.456fdsf, seul le 12 sera lu et il restera .456fdsf dans le buffer ! Tu gères ce cas comment ? Sans parler des mélanges de scanf et getchar qui sont catastrophiques !
Finalement, scanf est complètement déconseillé !
Déjà, il faut revenir à l'objectif initial. Est-ce que tu cherches simplement à savoir si un programme est déjà ouvert ou est-ce que tu veux vraiment faire un « mini-IRC », c'est-à-dire deux applications dialoguant ensembles sur la durée ?Quels seraient les point à modifier pour faire de ce programme un exemple simple ? Ne pas perdre l'idée qu'un exemple soit simple... C'est à dire que si l'exemple ne porte pas sur la récupération des entrées claviers, unscanf peut-être acceptable si on met un avertissement sur l'utilisation su scanf en début d'exemple.
Après, même si l'exemple est simple, il faut qu'il irréprochable sur tous les points (notamment la gestion des erreurs) car les gens qui suivront cet exemple ne le remettront pas en question. Et plus important encore, ils ne comprendront pas forcément en une seule fois tout ce qu'ils font. Il est donc important de leur donner d'emblée les bonnes habitudes.
Le but de cet exemple est l'utilisation, d'un socket AF_UNIX avec la possibilité pour le serveur de réceptionner plusieurs clients simultanément. Sans le select, on bloquait à la réception d'un client à la fois.
Il y a en plus l'originalité (c'était le but premier de ma demande), de choisir l'état client ou serveur s'il y a ou non un serveur déjà ouvert.
Et pour moi aussi de tout comprendre
Donc, là, je regarde la sortie du serveur que j'ai faite. Je n'ai fermé que le slot qui est à l'écoute d'une éventuelle nouvelle demande. Il faudrait sûrement que je rajoute la fermeture de tous les autres slots non ?
À ce niveau je suis désagréablement surpris par la présence obligatoire de "remove(SERVER_PATH)" pour supprimer "srwxrwxr-x 1 troumad troumad 0 mai 14 14:44 /tmp/server=".
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 } while (on); if (shutdown(sd,SHUT_RDWR)<0) { perror("echec de shutdown() "); } if (close(sd)<0) { perror("echec de close() "); } for (i=0;i<NCNX;++i) /* Parcourt les entrées possibles */ if (slot[i]>=0) /* Si l'entrée est encore active */ { if (shutdown(slot[i],SHUT_RDWR)<0) /* fermer l'entrée */ { perror("echec de shutdown() "); } if (close(slot[i])<0) /* fermer le descripyeur de fichier */ { perror("echec de close() "); } } remove(SERVER_PATH);
nb : je mettrais mon programme avec l'affichage de la présence d'erreurs quand j'aurais fini de relever les petites questions du genre afin de pouvoir modifier le plus longtemps possible la version que je mettrais en ligne
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager