/* mai08-forum1.c : Mini serveur de forum - correction * * Edouard.Thiel@lif.univ-mrs.fr - 12/05/2008 * * Compilation : gcc -Wall -o mai08-forum1 mai08-forum1.c bor-util.c * Usage : ./mai08-forum1 * * This program is free software under the terms of the * GNU Lesser General Public License (LGPL) version 2.1. */ #include "bor-util.h" int boucle_princ = 1; void capte_fin (int sig) { printf ("Serveur: signal %d capté\n", sig); boucle_princ = 0; } #define TEXTO_LEN 200 #define TEXTO_MAX 100 typedef struct { char buf[TEXTO_LEN]; /* sous la forme "[pseudo] texte" */ int num; /* numéro unique */ } Texto; int texto_uniq = 0; /* numéro unique incrémenté à chaque texto */ int texto_nb = 0; /* nombre de textos dans la liste */ Texto texto_tab[TEXTO_MAX]; /* la liste de textos en mémoire, [0..texto_nb-1] */ void memo_texto (char *pseudo, char *ligne) { /* Si plein, on décale tous les textos vers le bas, le #0 est supprime'. On utilise memmove car les zones de mémoires s'intersectent */ if (texto_nb < TEXTO_MAX) texto_nb++; else memmove (texto_tab, texto_tab+1, (TEXTO_MAX-1)*sizeof(Texto)); snprintf (texto_tab[texto_nb-1].buf, TEXTO_LEN, "[%s] %s\n", pseudo, ligne); texto_tab[texto_nb-1].num = texto_uniq++; } #define PSEUDO_LEN 40 #define REQ_LEN 250 /* > PSEUDO_LEN + TEXTO_LEN */ #define MSG_LEN 8000 /* > TEXTO_LEN*TEXTO_MAX */ typedef struct { struct sockaddr_in adr; /* Adresse du client */ int soc; /* Socket de service */ char req[REQ_LEN]; /* Buffer requête */ int req_ipos; /* Position courante d'insertion dans req */ char msg[MSG_LEN]; /* Buffer message réponse */ int msg_wpos; /* Position courante d'envoi de msg */ char pseudo[PSEUDO_LEN]; /* Pseudo du client */ int last_texto; /* Numéro du premier texto non lu */ } Client; #define CLI_MAX 100 Client cli_tab[CLI_MAX]; /* La liste des clients connectés, [0..cli_nb-1] */ int cli_nb = 0; /* Nombre de clients dans la liste */ int inserer_client (int soc_ser, struct sockaddr_in *adr) { if (cli_nb >= CLI_MAX) return -1; memcpy (&cli_tab[cli_nb].adr, adr, sizeof (struct sockaddr_in)); cli_tab[cli_nb].soc = soc_ser; cli_tab[cli_nb].req[0] = 0; cli_tab[cli_nb].req_ipos = 0; cli_tab[cli_nb].msg[0] = 0; cli_tab[cli_nb].msg_wpos = 0; cli_tab[cli_nb].pseudo[0] = 0; cli_tab[cli_nb].last_texto = 0; return cli_nb++; } /* Suppression en recopiant le dernier client à la place du client à supprimer => si on veut faire un parcours avec suppressions éventuelles, il faut faire le parcours à l'envers. */ void supprimer_client (int k) { close (cli_tab[k].soc); cli_tab[k] = cli_tab[--cli_nb]; } void poster_message (int k, char *buf) { Client *c = &cli_tab[k]; /* pour simplifier l'ecriture */ int j = strlen (c->msg); strncpy (c->msg + j, buf, MSG_LEN-j-1); c->msg[ MSG_LEN-1 ] = 0; /* securite */ } /* idem TP 8 */ int creer_socket_ecoute (int nport, int maxpend) { int soc_ec; struct sockaddr_in adrS; /* Création d'une socket domaine internet et mode connecté */ soc_ec = socket (AF_INET, SOCK_STREAM, 0); if (soc_ec < 0) { perror ("socket ip"); return -1; } /* Fabrication adresse du serveur */ adrS.sin_family = AF_INET; adrS.sin_port = htons (nport); /* 0 pour attribution d'un port libre */ adrS.sin_addr.s_addr = htonl(INADDR_ANY); /* Toutes les adr. locales */ /* Attachement socket à l'adresse du serveur */ printf ("Attachement socket serveur\n"); if (bor_bind_in (soc_ec, &adrS) == -1) { perror ("bind ip"); close (soc_ec); return -1; } /* Récupération du port sous forme Network */ if (bor_getsockname_in (soc_ec, &adrS) < 0) { perror ("getsockname ip"); close (soc_ec); return -1; } printf ("port %d ouvert\n", ntohs(adrS.sin_port)); /* Ouverture du service ; le second param est le nb max de connexions pendantes, limité à SOMAXCONN (=128 sur Linux) */ if (listen (soc_ec, maxpend) < 0) { perror ("listen"); return -1; } printf ("Serveur: en écoute sur %s\n", bor_adrtoa_in (&adrS)); return soc_ec; } int accepte_connexion (int soc_ec) { int k, soc_ser; struct sockaddr_in adrC_tmp; printf ("Serveur: connexion en cours ...\n"); soc_ser = bor_accept_in (soc_ec, &adrC_tmp); if (soc_ser < 0) { perror ("accept"); return -1; } k = inserer_client (soc_ser, &adrC_tmp); if (k < 0) { close (soc_ser); printf ("Serveur: connexion refusée avec %s : trop de clients\n", bor_adrtoa_in(&adrC_tmp)); } else { printf ("Serveur : connexion établie avec %s\n", bor_adrtoa_in(&adrC_tmp)); poster_message (k, "\n*** Bienvenue sur le forum ***\n\nTapez ':h' pour l'aide\n"); } return 0; } /* Renvoie 1 si le client est supprimé, 0 sinon */ int analyser_requete (int k) { Client *c = &cli_tab[k]; /* pour simplifier l'ecriture */ char mot1[256], buf[256]; int pos; printf ("ANALYSE \"%s\"\n", c->req); if (sscanf (c->req, "%s %n", mot1, &pos) < 1) return 0; if (strcmp (mot1, ":h") == 0) { poster_message (k, "\n" "':p' pseudo : donner son pseudo\n" "':r' : lire les nouveaux textos\n" "':w' texte : envoyer un texto\n" "':q' : quitter\n" "\n"); } else if (strcmp (mot1, ":q") == 0) { supprimer_client (k); return 1; } else if (strcmp (mot1, ":p") == 0) { strncpy (c->pseudo, c->req + pos, PSEUDO_LEN-1); c->pseudo[PSEUDO_LEN-1] = 0; /* securite */ sprintf (buf, "Votre pseudo est [%s]\n", c->pseudo); poster_message (k, buf); } else if (strcmp (mot1, ":r") == 0) { int i, j; if (texto_nb == 0) poster_message (k, "Le forum est vide\n"); else if (c->last_texto == texto_uniq) poster_message (k, "Rien de nouveau\n"); else { /* Envoie les 20 textos suivants */ for (i = 0, j = 0; i < texto_nb && j < 20; i++) if (texto_tab[i].num >= c->last_texto) { poster_message (k, texto_tab[i].buf); c->last_texto = texto_tab[i].num+1; j++; } } } else if (strcmp (mot1, ":w") == 0) { if (c->pseudo[0] == 0) poster_message (k, "Donner d'abord un pseudo avant d'ecrire !\n"); else memo_texto (c->pseudo, c->req+pos); } else poster_message (k, "Erreur de syntaxe. Tapez ':h' pour l'aide\n"); return 0; } /* On s'inspire du TD7.II, "défragmentation de lignes" avec recherche "\n" ; attention ici on cherche "\r\n" */ void traiter_requete (int k) { int j, a, b, i; Client *c = &cli_tab[k]; /* pour simplifier l'ecriture */ j = read (c->soc, c->req + c->req_ipos, REQ_LEN - c->req_ipos - 1); if (j < 0) { perror ("read"); supprimer_client (k); return; } if (j == 0) { printf ("fin client\n"); supprimer_client (k); return; } /* Recherche marqueur de fin ^M^J = \r\n (envoye' par telnet avec touche Entre'e) */ a = c->req_ipos - 1; if (a < 0) a = 0; b = c->req_ipos + j-1; for (i = a; i < b; i++) if (c->req[i] == '\r' && c->req[i+1] == '\n') { c->req[i] = 0; /* On termine la chaine */ if (analyser_requete (k) == 1) return; /* client supprimé */ j = c->req_ipos + j - (i+2); /* nb de car restant à analyser */ memmove (c->req, c->req+i+2, j); c->req_ipos = 0; b = j-1; i = -1; /* revient au debut de req */ } c->req_ipos += j; if (c->req_ipos >= REQ_LEN-2) { poster_message (k, "Erreur: depassement, contenu supprimé\n"); c->req_ipos = 0; /* Dépassement */ } } void traiter_message (int k) { Client *c = &cli_tab[k]; /* pour simplifier l'ecriture */ int j; j = write (c->soc, c->msg + c->msg_wpos, strlen(c->msg) - c->msg_wpos); if (j < 0) { perror ("write"); supprimer_client (k); return; } c->msg_wpos += j; if (c->msg[c->msg_wpos] == 0) { /* On a tout envoye' */ c->msg_wpos = 0; c->msg[0] = 0; } } /* */ void init_select (int soc_ec, int *maxfd, fd_set *set_read, fd_set *set_write) { int k; FD_ZERO (set_read); FD_ZERO (set_write); FD_SET (soc_ec, set_read); *maxfd = soc_ec; for (k = 0; k < cli_nb; k++) { /* On scrute tjrs en lecture */ FD_SET (cli_tab[k].soc, set_read); if (cli_tab[k].soc > *maxfd) *maxfd = cli_tab[k].soc; /* En ecriture on scrute si rep a envoyer */ if (cli_tab[k].msg[ cli_tab[k].msg_wpos ] != 0) { FD_SET (cli_tab[k].soc, set_write); if (cli_tab[k].soc > *maxfd) *maxfd = cli_tab[k].soc; } } } int main () { int soc_ec = -1, maxfd, res, k; fd_set set_read, set_write; soc_ec = creer_socket_ecoute (0,8); if (soc_ec < 0) goto fin_serveur; bor_signal (SIGINT, capte_fin, SA_RESTART); while (boucle_princ) { init_select (soc_ec, &maxfd, &set_read, &set_write); res = select (maxfd+1, &set_read, &set_write, NULL, NULL); if (res > 0) { /* Recherche des sockets éligibles */ if (FD_ISSET (soc_ec, &set_read)) if (accepte_connexion(soc_ec) < 0) goto fin_serveur; /* Parcours inverse car un client peut etre supprime' */ for (k = cli_nb-1; k >= 0; k--) if (FD_ISSET (cli_tab[k].soc, &set_read)) traiter_requete (k); for (k = cli_nb-1; k >= 0; k--) if (FD_ISSET (cli_tab[k].soc, &set_write)) traiter_message (k); } else if (res < 0) { if (errno == EINTR) ; /* Signal reçu, on ne fait rien */ else { perror ("select"); goto fin_serveur; } } } fin_serveur: printf ("Serveur: fermeture sockets ...\n"); if (soc_ec != -1) close (soc_ec); for (k = cli_nb-1; k >= 0; k--) supprimer_client (k); exit (0); }