Vous êtes sur la page 1sur 4

Institut Galilée 9 mai 2012

Correction TP Sockets : multi-chat


• Exercice 1 : L’objectif de cet exercice est de créer une application de multi-chat avec TCP. Le principe de fonctionnement est le
suivant : Un programme serveur joue le rôle de noeud central. Il reçoit toutes les demandes de connexions des clients qui participent
au chat. Chaque ligne écrite par un client est envoyé vers ce serveur. Celui-ci en fait une copie et retransmet le texte à tous les
autres clients. Le serveur doit donc être en mesure d’écouter sur une socket plusieurs demandes de connexions possibles. Parmi toutes
les connexions ouvertes, il doit aussi identifier la socket sur laquelle une écriture a eu lieu. La primitive accept étant bloquante son
utilisation ne permettrait pas à serveur d’écouter en même temps les écritures sur les sockets ouvertes. Une solution consiste à utiliser
la primitive select.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 2020

int main(int argc, char *argv[])


{
int sock_cnx; /* Socket pour ouverture de connexion */
struct sockaddr_in serveraddr;
struct sockaddr_in clientaddr;
int newfd;
char buf[1024];
int nbytes;
int addrlen;
int i, j;
fd_set surveil_fds; /* Ensemble des descripteurs qu’on souhaite surveiller en lecture */
fd_set read_fds; /* Ensemble des descripteurs qu’on va utiliser dans SELECT */
int fdmax; /* Memorise le plus grand descripteur : à utiliser dans SELECT*/

addrlen = sizeof(clientaddr);
/* Ouverture de la socket du serveur */
if ((sock_cnx = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Erreur socket");
exit(1);
}
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
bzero(&(serveraddr.sin_zero), 8);
/* Bind */
if (bind(sock_cnx, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) {
perror("Erreur Bind");
exit(1);
}
/* Ecoute sur la socket, on autorise jusqu’a 10 requetes clients en attente */
if (listen(sock_cnx, 10) == -1) {
perror("Erreur Listen");
exit(1);
}
/* On initialize surveil_fds comme étant un ensemble vide */
FD_ZERO(&surveil_fds);
/* Positionne le bit associé à soc_cnx a 1 */
/* Grace à SELECT on pourra surveiller en ecoute soc_cnx sans à voir à utiliser la primitive bloquante accept */
FD_SET(sock_cnx, &surveil_fds);
fdmax = sock_cnx;
while (1)
{
Institut Galilée 9 mai 2012

read_fds = surveil_fds;
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1)
{
perror("Erreur select");
exit(1);
}
/* Nous allons parcourir l’ensemble des descripteurs pour savoir qui a débloqué select */
for (i = 0; i <= fdmax; i++)
{
if (FD_ISSET(i, &read_fds))
{
/* si le descripteur qui a debloqué select est soc_cnx */
/* alors on accepte l’ouvertur d’une connexion avec le nouveau client */
if (i == sock_cnx)
{
if ((newfd = accept(sock_cnx, (struct sockaddr *)&clientaddr, (int *)&addrlen)) == -1)
perror("Erreur accept");
else
{
/* On ajoute le nouveau descripteur dans l’ensemble des descripteurs */
FD_SET(newfd, &surveil_fds);

/* On met à jour la valeur du plus grand descripteur */


if (newfd > fdmax) fdmax = newfd;
printf("%s: Nouvelle connexion de %s \n", argv[0], inet_ntoa(clientaddr.sin_addr));
}
}
/* Sinon c’est un ecriture d’un client sur la socket associé au descripteur i*/
/* Auquel cas, on va lire ce message et l’envoyer sur toutes les autres sockets */
/* excepté sur sock_cnx et sur i */
else
{
if ((nbytes = recv(i, buf, sizeof(buf), 0)) <= 0)
{
/* si nbytes = 0 c’est que le client a fermé la connexion */
/* si nbytes < 0 alors erreur d’écriture sur la socket */
/* dans les deux cas on ferme la connexion */
close(i);
/* on enleve le descripteur i de l’ensemble des descripteurs */
FD_CLR(i, i, &surveil_fds);
}
else
{
for (j = 0; j <= fdmax; j++)
if (FD_ISSET(j, &surveil_fds) && (j != sock_cnx) && (j != i))
if (send(j, buf, nbytes, 0) == -1)
perror("Erreur send");
}
}
}
}
}
return 0;
}

Pour tester ce programme vous pouvez

1. Tapez : telnet @ip du serveur 2020


2. Complétez les lignes manquantes du programme client donné dans cet énoncé.
Institut Galilée 9 mai 2012

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <signal.h>

int main(int argc, char** argv)


{
char* id = 0;
short sport = 0;
int sock = 0;
pid_t pid;
struct sockaddr_in moi;
struct sockaddr_in serveur;
int ret;
socklen_t len;
char buf_read[256];

if (argc != 4) {
fprintf(stderr,"usage: %s pseudo @IPserveur port\n",argv[0]);
exit(1);
}
id = argv[1];
sport = atoi(argv[3]);

if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {


fprintf(stderr,"%s: socket %s\n", argv[0],strerror(errno));
exit(1);
}

serveur.sin_family = AF_INET;
serveur.sin_port = htons(sport);
inet_aton(argv[2],(struct in_addr *)&serveur.sin_addr);
bzero(&(serveur.sin_zero), 8);

socklen_t s = sizeof(serveur);
if (connect(sock,(struct sockaddr *)&serveur,s) < 0) {
fprintf(stderr,"%s: connect %s\n",argv[0],strerror(errno));
perror("bind");
exit(1);
}

len = sizeof(moi);
getsockname(sock,(struct sockaddr *)&moi,&len);
if ((pid = fork()) < 0) {
printf("erreur fork\n");
exit(-1);
}
else if (pid==0) for(;;) {
ret = read(sock,buf_read,256);
if (ret <= 0) {
printf("%s:erreur dans read (num=%d,mess=%s)\n", argv[0], ret, strerror(errno));
exit(2);
}
printf("%s\n",buf_read);
}
else {
char message[200];
while (strcmp(message, "q") != 0) {
char buf_write[256];
fgets(message, 100, stdin);
if (message[strlen(message)-1] == ’\n’) message[strlen(message)-1] = 0;
Institut Galilée 9 mai 2012

sprintf(buf_write, "%s : %s", id, message);


ret = write(sock,buf_write,strlen(buf_write)+1);
if (ret <= strlen(buf_write)) {
printf("%s: erreur dans write (num=%d,mess=%s)\n",argv[0],ret,strerror(errno));
continue;
}
}
printf("Connexion close\n");
kill(pid, SIGKILL);
shutdown(sock, SHUT_RDWR);
}
return 0;
}

3. Adaptez ce programme client pour avoir une version basée sur la primitive select permettant à un seul processus de surveiller
en parallèle l’entrée standard et la connexion avec le serveur de chat.

Vous aimerez peut-être aussi