Forum Programmation.c Serveur de tchat

Posté par  .
Étiquettes : aucune
0
11
sept.
2004
j'ai commencé un serveur de tchat en C, et évidement, comme bcp de débutants (j'éspère ^^) j'ai un problème avec select...

j'arrives pas a l'utiliser malgré ce qui était écrit dans le linux mag 41...

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>

#define MYPORT 1789

#define NOMBREMAX 20

typedef struct{
char pseudo[20];
} client;

int envoi(int a, int b, char *chaine){
int retour;
retour=send(a, chaine, strlen(chaine), b);
if (retour == -1){
perror("send");
close(a);
}
return retour;
}

main(){
char msg[4097];
int len;
int sockfd, nombre=0, i, j, k;
client tchateur[NOMBREMAX];
struct sockaddr_in my_addr, their_addr;
int sin_size;
int new_fd[NOMBREMAX];
struct timeval tv;
int retval;
fd_set rfds;
tv.tv_sec=0;
tv.tv_usec=200;

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");
exit(1);
}
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero), 8);
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1){
perror("bind");
exit(1);
}
while (1)
{
//recherche un nouveau pour se connecter
if ( listen(sockfd, NOMBREMAX)== -1){
perror("listen");
exit(1);
}
sin_size = sizeof(struct sockaddr_in);
if ((new_fd[nombre] = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1){
perror("accept");
printf("ERREUR...\n");
}
printf("nouveau connecté : %s\n", inet_ntoa(their_addr.sin_addr));
envoi(new_fd[nombre], 0, "\n\n\t\tBienvenu sur le serveur de Max\nListe des commandes :\n\tEXIT pour quitter.\n\tLISTE PSEUDO pour avoir la liste des personnes connectés\nPseudo : ");
len = read(new_fd[nombre], tchateur[nombre].pseudo, 19);
tchateur[nombre].pseudo[19]=0;
//finit le pseudo car si il est plus grand que 20 caractères alors il aurait été tronqué...
nombre++; //un connecté de plus
for (i=0; i<nombre-1; i++) //préviens tt le monde sauf le nouveau
{
envoi(new_fd[i], 0, "Connexion de :");
envoi(new_fd[i], 0, tchateur[nombre-1].pseudo);
}
//fin de le recherche de nouveau connecté
for (j=0; j<4097; j++){
msg[j]=0;
}
len = read(new_fd[i], msg, 4096);
if ( msg[0]==69 && msg[1]==88 && msg[2]==73 && msg[3]==84 && msg[3]==84 )
{ //Si la comende "EXIT" est tapée alors on déconnecte le client et on refraihit la liste
close(tchateur[i].new_fd);
printf("déconnexion de : %s", tchateur[i].pseudo);
for (j=0;j<nombre;j++){ //préviens tt le monde que machin s'est déconnecté
if (j!=i){ //i est déconnecté
envoi(new_fd[j], 0, "déconnexion de : ");
envoi(new_fd[j], 0, tchateur[i].pseudo);
}
}
for (j=i; j<nombre-1; j++){ //refraichit la liste
new_fd[j]=new_fd[j+1];
for (k=0;k<20;k++){
tchateur[j].pseudo[k]=tchateur[j+1].pseudo[k];
}
}
nombre--;
}
else if(msg[0]==76 && msg[1]==73 && msg[2]==83 && msg[3]==84 && msg[4]==69)
{ //Si "LISTE" est tapée alors
for (j=0;j<nombre;j++){ //on affiche tt les pseudos
envoi(new_fd[i], 0, tchateur[j].pseudo);
}
}else{
for ( j=0; j < nombre; j++) //envoi à tt le monde ce que i dit
{
if (j != i){ //mais ne l'envoi pas a i
envoi(new_fd[j], 0, tchateur[i].pseudo);
envoi(new_fd[j], 0, "\t");
envoi(new_fd[j], 0, msg);
}
}
}
}
}



je sais déja que même si la j'arrives a faire qqch, le serveur de bloqueras à chaque nouvelle connecion, si la personne ne mets pas de pseudo, mais c'ets un détail de dèrnière minute, la, je cherches a faire fonctionner select... et je n'ai aps mis select ici, car je ne sais même pas comment faire pour l'utiliser mis a part l'ini des variables et encore pas toutes loin de la, juste leur déclaration...
  • # Select

    Posté par  . Évalué à 2.

    Algo vite fait :
    /* set de sockets écouteurs */
    fd_set fdSetEcouteurs;
    /* Controle de la boucle */
    int bTourner = 1;
    /* Tableau de sockets qui écoutent */
    int *skTabEcouteurs = NULL;
    /* Effectif du tableau */
    int iNbEcouteurs = 0;
    /* Retour de select() */
    int iRet = 0;
    
    while( bTourner )
    {
      /* Nettoie le set de descripteurs/sockets */
      FDZERO(&fdLecture); FDZERO(&fdEcriture); FDZERO(&fdException);
    
      /* On le remplit avec nos écouteurs */
      for (i=0; i < iNbEcouteurs; i++) { FDSET(skTabEcouteurs[i], fdSetEcouteurs); }
    
      /* Select */
      iRet = select(i,  &fdSetEcouteurs,  NULL,     NULL,      NULL);
      /*                ^            ^                 ^             ^               ^     */
      /*         nb sockets   lecteurs    'écriveurs'  exceptions   timeout */
      /* Dans la pratique, mieux vaux un timeout ;) */
      /* (Le nombre de sockets n'est pas nécessaire sous Win32) */
      
      /* On teste si tout s'est bien passé */
      if (iRet != 0) { perror("COIN!"); exit(1); }
      
      /* Select a enlevé du set tous les écouteurs qui n'ont rien à dire */
      /* On teste donc ce qui reste dans le set de départ */
      for (i=0; i < iNbEcouteurs; i++)
      {
         if( FDISSET(skTabEcouteurs[i], fdSetEcouteurs) )
         {
           /* Il y a de la lecture sur ce socket */
           printf("Le socket %i a quelque chose à dire !\n", i);
         }
       }
       /* Et c'est reparti pour un tour... */
    }
    
    • [^] # Re: Select

      Posté par  . Évalué à 2.

      Patch:

      --- FDZERO(&fdLecture); FDZERO(&fdEcriture); FDZERO(&fdException);
      +++ FDZERO(&fdSetEcouteur);

      Et toutes les macros FD** demandent l'adresse du set, même si j'ai oublié quelques '&' au passage...
      • [^] # Re: Select

        Posté par  . Évalué à 1.

        OK merci bcp, je renvois le code final dès que ça marche.
        ça devrait m'être très utile.
      • [^] # Re: Select

        Posté par  . Évalué à 1.

        c'est FD_ZERO et pareil pour tt les fonctions...

        j'ai testé, mais ça marche toujours pas... je n'arrives pas a me connecter au serveur...
    • [^] # Re: Select

      Posté par  (site web personnel) . Évalué à 2.

      /* Dans la pratique, mieux vaux un timeout ;) */

      Pourquoi ?
  • # utorial sur les socket et 'select' en particulier

    Posté par  . Évalué à 2.

    tu trouveras là (http://perso.club-internet.fr/nicolas-laurent/C/socket.html(...)) un petit tutoriel sur les sockets que j'ai écrit il y a quelques temps. La partie sur l'appel à "select" est détaillée ici:
    http://perso.club-internet.fr/nicolas-laurent/C/socket-13.html(...)

    Dans ton code, c'est le accept() qui est bloquant. La bonne solution est bien d'utiliser select().


    Bon courage.
    • [^] # Re: utorial sur les socket et 'select' en particulier

      Posté par  . Évalué à 1.

      je savais ce qui était bloquant (j'ai bossé pas mal de temps dessus, c'est mon premier programme réseau...) donc, il n'y a pas que accet, mais aussi read...

      Donc, maintenant mon problème est : il ne sort jamais de select, même si je me connecte au serveur...

      Donc, voila, même si je n'ai pas de messages d'erreurs à la complation, je suis toujours coincé ^^...

      merci pour ton tuto, je le lis la, et il a l'air plutot complète et bien expliqué bravo !
      • [^] # Re: utorial sur les socket et 'select' en particulier

        Posté par  . Évalué à 2.

        > Donc, maintenant mon problème est : il ne sort jamais de select, même si je me connecte au serveur...
        Si tu as utilisé mon code succint, c'est normal : c'est l'algo d'un mini serveur qui ne fait qu'écouter et signaler s'il y a des données sur les sockets. Déplace donc ton code de gestion de connexion dans le if(FD_ISSET(....)) pour plus de résultats.
        Sinon, tu auras du mal à faire un serveur gérant plusieurs connexions sans faire appels aux threads, pour éviter la gène des opérations bloquantes et pouvoir traiter simultanément différents clients.
        Autre option, utiliser des i/o non bloquantes sur tes sockets, mais ça utilise plus de CPU.
        • [^] # Re: utorial sur les socket et 'select' en particulier

          Posté par  . Évalué à 1.

          voila mon code, c'ets possible que je ne passe pas les bons arguments a DF_SET, en fait, le réseau, j'y comprends pas grand chose, la je code sans vraiment savoir ou sont les variables qui seront modifiés, "comme" quand on débute ... en faisant du copier coller de plusieurs programmes pour les réassembler en éspèrant obtennir qqch de bien ^^


          #include <stdio.h>
          #include <stdlib.h>
          #include <errno.h>
          #include <string.h>
          #include <sys/types.h>
          #include <netinet/in.h>
          #include <sys/socket.h>
          #include <sys/wait.h>
          #include <sys/select.h>
          #include <sys/time.h>
          #include <unistd.h>

          #define MYPORT 1988

          #define NOMBREMAX 20

          typedef struct{
          char pseudo[20];
          int client_fd;
          } client;

          int envoi(int a, int b, char *chaine){
          int retour;
          retour=send(a, chaine, strlen(chaine), b);
          if (retour == -1){
          perror("send");
          close(a);
          }
          return retour;
          }

          int main(){
          char msg[4097];
          int len;
          int nombre=0, i, j, k;
          client tchateur[NOMBREMAX];
          struct sockaddr_in my_addr, their_addr;
          int sin_size;
          int serveur_fd;
          fd_set rfds;
          if ((serveur_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
          perror("socket");
          exit(1);
          }
          my_addr.sin_family = AF_INET;
          my_addr.sin_port = htons(MYPORT);
          my_addr.sin_addr.s_addr = INADDR_ANY;
          bzero(&(my_addr.sin_zero), 8);
          if (bind(serveur_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1){
          perror("bind");
          exit(1);
          }
          if ( listen(serveur_fd, NOMBREMAX)== -1){
          perror("listen");
          exit(1);
          }
          //recherche un nouveau pour se connecter
          sin_size = sizeof(struct sockaddr_in);
          while (1)
          {
          FD_ZERO(&rfds);
          FD_SET(serveur_fd,&rfds);
          for (i=0; i < nombre+1; i++)
          FD_SET(tchateur[i].client_fd,&rfds);
          printf("lancement de select\n");
          select(nombre+2, &rfds, NULL, NULL, NULL);
          if ( FD_ISSET(serveur_fd, &rfds)){
          //recherche un nouveau pour se connecter
          if ((tchateur[nombre].client_fd = accept(serveur_fd, (struct sockaddr *)&their_addr, &sin_size)) == -1){
          perror("accept");
          printf("ERREUR...\n");
          }
          printf("nouveau connecté...\n");
          envoi(tchateur[nombre].client_fd, 0, "\n\n\t\tBienvenu sur le serveur de Max\nListe des commandes :\n\tEXIT pour quitter.\n\tLISTE PSEUDO pour avoir la liste des personnes connectés\nPseudo : ");
          len = read(tchateur[nombre].client_fd, tchateur[nombre].pseudo, 19);
          tchateur[nombre].pseudo[19]=0;
          //finit le pseudo car si il est plus grand que 20 caractères alors il aurait été tronqué...
          nombre++; //un connecté de plus
          for (i=0; i<nombre-1; i++) //préviens tt le monde sauf le nouveau
          {
          envoi(tchateur[i].client_fd, 0, "Connexion de :");
          envoi(tchateur[i].client_fd, 0, tchateur[nombre-1].pseudo);
          }

          }
          for (i=0;i<nombre;i++){
          if (FD_ISSET(tchateur[i].client_fd,&rfds))
          {
          for (j=0; j<4097; j++)
          msg[j]=0;
          len = read(tchateur[i].client_fd, msg, 4096);
          if ( msg[0]==69 && msg[1]==88 && msg[2]==73 && msg[3]==84 && msg[3]==84 )
          { //Si la comende "EXIT" est tapée alors on déconnecte le client et on refraihit la liste
          close(tchateur[i].client_fd);
          printf("déconnexion de : %s", tchateur[i].pseudo);
          for (j=0;j<nombre;j++){ //préviens tt le monde que machin s'est déconnecté
          if (j!=i){ //i est déconnecté
          envoi(tchateur[j].client_fd, 0, "déconnexion de : ");
          envoi(tchateur[j].client_fd, 0, tchateur[i].pseudo);
          }
          }
          for (j=i; j<nombre-1; j++){ //refraichit la liste
          tchateur[j].client_fd=tchateur[j+1].client_fd;
          for (k=0;k<20;k++){
          tchateur[j].pseudo[k]=tchateur[j+1].pseudo[k];
          }
          }
          nombre--;
          }
          else if(msg[0]==76 && msg[1]==73 && msg[2]==83 && msg[3]==84 && msg[4]==69)
          { //Si "LISTE" est tapée alors
          for (j=0;j<nombre;j++){ //on affiche tt les pseudos
          envoi(tchateur[i].client_fd, 0, tchateur[j].pseudo);
          }
          }else{
          for ( j=0; j < nombre; j++) //envoi à tt le monde ce que i dit
          {
          if (j != i){ //mais ne l'envoi pas a i
          envoi(tchateur[j].client_fd, 0, tchateur[i].pseudo);
          envoi(tchateur[j].client_fd, 0, "\t");
          envoi(tchateur[j].client_fd, 0, msg);
          }
          }
          }
          }
          }
          }
          return 0;
          }
        • [^] # Re: utorial sur les socket et 'select' en particulier

          Posté par  (site web personnel) . Évalué à 3.

          Sinon, tu auras du mal à faire un serveur gérant plusieurs connexions sans faire appels aux threads, pour éviter la gène des opérations bloquantes et pouvoir traiter simultanément différents clients.

          Le problème c'est que ça complexifie beaucoup le programme (mutex), je ne suis pas sûr que ça vaille le coup pour un serveur qui calcule rapidement la réponse (i.e. sans appel à une base de données ni à d'autres ressources réseaux distantes).
  • # J'AI TROUVE

    Posté par  . Évalué à 1.

    En fait, faut pas mettre max+1 en premier argument de select, mais max_fd +1 donc, ça ne pouvait pas marcher, j'ai donc modifié pas mal de trucs, et la verstion finale sera bientot sur mon site (dès la prochaine mise a jours)

    http://coucou747.hopto.org(...)

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.