Serveur de chat multithreade en c sous linux

Soyez le premier à donner votre avis sur cette source.

Vue 18 625 fois - Téléchargée 1 738 fois

Description

Voila un petit serveur de chat en C sous Linux. Il permet de gerer de nombreux clients dans un seul processus, et peut servir d exemple pour creer un serveur avec les thread. La protection des donnees est assuree avec des mutex sur les structures globales. Ce serveur inclut les fonctionnalites standards, c est a dire la gestion des pseudo, des droits admin, certaines commandes,etc... Il peut etre facilement complete. De plus il contient des fonctions utiles pour la programmation reseau sous Linux. Il n y a *en principe* pas de bug.
Ce code compile sans probleme sous Linux 2.6 (2.6.7 pour etre precis) avec gcc (3.4.0). Il devrait fonctionner sans probleme sur tout Unix compatible POSIX pour les threads, et sans doute sur Linux 2.4 (ce qui n est pas totalement sur a cause d une gestion partielle des threads dans Linux 2.4, mais en principe aucune des nouvelles fonctionnalites de Linux 2.6 n est utilisee).

Source / Exemple :


/* serveur de chat sous Linux avec les thread */
/* gcc server.c -o server -lpthread -D_REENTRANT */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <pthread.h>

#define BANNER "Serveur de chat v0.4 par .:MiniMoi:.\n(C) .:MiniMoi:. 2004\n"
#define CLIENT_BANNER "Connection etablie...\r\nBienvenue sur le serveur de chat de MiniMoi\r\nEntrez votre pseudo: "
#define ADMIN_PWD "MiniMoi"
#define HELP_MSG "Commandes disponibles :\r\n- /pseudo=[nouveau pseudo] --> changer de pseudo\r\n- /quit=[message] --> quitter avec (ou sans) message\r\n- /list --> obtenir la liste des clients connectes\r\n- /admin=[password] --> obtenir les droits administrateur\r\n- /kick=[pseudo] --> kicker un client(reserve aux admins)\r\n- /? --> afficher cette aide\r\n"

#define MAX_CLIENTS 500
#define LS_CLIENT_NB 5
#define INVALID_SOCKET -1
#define PORT 1987

volatile int nb_clients = 0;
int first_free = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

typedef struct _s_client
{
	pthread_t id;
	int sock;
	char *pseudo;
	char admin;
} s_client;

s_client *clients[MAX_CLIENTS];

/* creation d'un serveur */
int create_server(int port)
{
  int sock,optval = 1;
  struct sockaddr_in sockname;

  if((sock = socket(PF_INET,SOCK_STREAM,0))<0)
    {
      printf("Erreur d'ouverture de la socket");
      exit(-1);
    }
  
  setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(int));
  memset((char *) &sockname,0,sizeof(struct sockaddr_in));
  sockname.sin_family = AF_INET;
  sockname.sin_port = htons(port);
  sockname.sin_addr.s_addr = htonl(INADDR_ANY);

  if(bind(sock,(struct sockaddr *) &sockname, sizeof(struct sockaddr_in)) < 0)
    {
      printf("Erreur de bind!");
      exit(-1);
    }

  if(listen(sock,LS_CLIENT_NB) <0)
    {
      printf("listen error!");
      exit(-1);
    }
  
  return sock;
}

/* accepte une connexion avec ou sans timeout */
int server_accept(int main_sock,int timeout)
{
  int sock;

  if(timeout > 0)
    alarm(timeout);

  if((sock = accept(main_sock,NULL,0)) < 0)
    {
      if(errno == EINTR)
	{
	  shutdown(main_sock,SHUT_RDWR);
	  close(main_sock);
	  if(timeout > 0)
	    alarm(0);
	  return -1;
	}
      else
	{
	  printf("\nAccept error.\n");
	  exit(-1);
	}
    }

  if(timeout > 0)
    alarm(0);
  fcntl(sock,F_SETFD,1);
  
  return sock;
}

/* envoyer une chaine de caractere a un client */
int send_msg(int sock,char *msg)
{
	return write(sock,msg,strlen(msg));
}

/* envoyer un message a tout le monde sauf a la socket not_to */
int send_all(char *msg, int not_to)
{
	int i;
	
	pthread_mutex_lock(&mutex);	// debut de la section critique
	for(i=0;i<first_free;i++)
	{
		if(clients[i]->sock != not_to)
			send_msg(clients[i]->sock,msg);
	}
	pthread_mutex_unlock(&mutex);	// fin de la section critique
	
	return 0;
}

/* gestion de fin de connection d'un client */
void client_quit(s_client *me, char *msg)
{
	int i,j;
	char buf[8192+1];
	
	if(msg)	snprintf(buf,8192,"%s nous quitte...(%s)\r\n",me->pseudo,msg);
	else	snprintf(buf,8192,"%s nous quitte...\r\n",me->pseudo);
	buf[8192] = '\0';
	send_all(buf,me->sock);
	pthread_mutex_lock(&mutex);	// debut de la section critique
	for(i=0;(clients[i]->sock != me->sock);i++);	// recherche de l'index de la structure dans le tableau
	
	close(me->sock);
	free(me->pseudo);
	free(me);
	
	for(j=i+1;j<first_free;j++)	// on reorganise le tableau en decalant les elements situes APRES celui qui est supprime
	{
		clients[j-1] = clients[j];
	}
	nb_clients--;
	first_free--;
	pthread_mutex_unlock(&mutex);	// fin de la section critique
	printf("Un client en moins...%d clients\n",nb_clients);
}

/* interaction avec le client (thread) */
void *interact(void *param)
{
	int sck = *((int *) param);
	char msg[4096+1];
	char msg_to_send[8192+1];
	s_client *me = NULL;
	char *buf = NULL;
	int len;
	int i;
	
	me = (s_client *) malloc(sizeof(s_client));
	if(!me)
	{
		printf("\nErreur d'allocation memoire!\n");
		close(sck);
		nb_clients--;
		pthread_exit(NULL);
	}
	bzero(me,sizeof(s_client));
	
	send_msg(sck,CLIENT_BANNER);
	len = read(sck,msg,4096);
	if(len <= 0)
	{
		printf("\nErreur\n");
		close(sck);
		free(me);
		me = NULL;
		nb_clients--;
		pthread_exit(NULL);
	}
	msg[255] = '\0';	// on limite le pseudo a 255 caracteres
	for(i=0;(msg[i]!='\0') && (msg[i]!='\r') && (msg[i]!='\n') && (msg[i]!='\t');i++);
	msg[i] = '\0';	// on isole le pseudo
	
	pthread_mutex_lock(&mutex);	// debut de la section critique
	for(i=0;i<first_free;i++)
	{
		if(!strcmp(msg,clients[i]->pseudo))
		{
			send_msg(sck,"\r\nPseudo deja utilise! Deconnection...\r\n");
			close(sck);
			free(me);
			nb_clients--;
			pthread_mutex_unlock(&mutex);	// fin de la section critique
			pthread_exit(NULL);
		}
	}
	pthread_mutex_unlock(&mutex);	// fin de la section critique
	
	me->id = pthread_self();
	me->sock = sck;
	me->pseudo = strdup(msg);
	me->admin = 0;
	
	pthread_mutex_lock(&mutex);	// debut de la section critique
	clients[first_free] = me;
	first_free++;
	pthread_mutex_unlock(&mutex);	// fin de la section critique
	
	snprintf(msg_to_send,8192,"Nouveau client : %s\r\n",me->pseudo);
	msg_to_send[8192]='\0';
	send_all(msg_to_send,INVALID_SOCKET);
	while(1)
	{
		len = read(sck,msg,4096);
		if(len <= 0)
		{
			client_quit(me,"Erreur reseau");
			pthread_exit(NULL);
		}
		msg[len] = '\0';
		if(msg[0] == '/')	// le message est une commande
		{
			int valid_command = 0;
			
			if(!strncmp(msg,"/pseudo=",8))	// changement de pseudo
			{
				char *old_pseudo = NULL;
				int valid_pseudo = 1;
				
				msg[255+8] = '\0';	// on limite le pseudo a 255 caracteres
				for(i=8;(msg[i]!='\0') && (msg[i]!='\r') && (msg[i]!='\n') && (msg[i]!='\t');i++);
				msg[i] = '\0';	// on isole le pseudo
				
				/* on verifie que le nouveau pseudo n'existe pas deja */
				pthread_mutex_lock(&mutex);	// debut de la section critique
				for(i=0;i<first_free;i++)
				{
					if(!strcmp(&msg[8],clients[i]->pseudo))
						valid_pseudo = 0;
				}
				pthread_mutex_unlock(&mutex);	// fin de la section critique
								
				if(valid_pseudo)
				{
					old_pseudo = me->pseudo;
					me->pseudo = strdup(&msg[8]);
					snprintf(msg_to_send,8192,"%s s'appelle maintenant %s\r\n",old_pseudo,me->pseudo);
					free(old_pseudo);
					send_all(msg_to_send,INVALID_SOCKET);
				}
				else send_msg(sck,"Pseudo deja utilise!\r\n");
				valid_command = 1;
			}
			if(!strncmp(msg,"/quit",5))	// sortie "propre" du serveur (avec message)
			{
				int i;
				
				if(msg[5]=='=')
				{
					for(i=6;(msg[i]!='\0') && (msg[i]!='\r') && (msg[i]!='\n') && (msg[i]!='\t');i++);
					msg[i]='\0';
					client_quit(me,&msg[6]);
				}
				else client_quit(me,NULL);
				pthread_exit(NULL);
				valid_command = 1;	// inutile car pthread_exit() quitte le thread...
			}
			if(!strncmp(msg,"/list",5))	// obtenir la liste des pseudos sur le serveur
			{
				pthread_mutex_lock(&mutex);	// debut de la section critique
				for(i=0;i<first_free;i++)
				{
					send_msg(me->sock,clients[i]->pseudo);
					send_msg(me->sock,"\r\n");
				}
				pthread_mutex_unlock(&mutex);	// fin de la section critique
				valid_command = 1;
			}
			if(!strncmp(msg,"/admin=",7))	// droits admin (avec mot de passe)
			{
				if(!strncmp(&msg[7],ADMIN_PWD,strlen(ADMIN_PWD)))
				{
					send_msg(me->sock,"Droits administrateur actives.\r\n");
					me->admin = 1;
				}
				else send_msg(me->sock,"Mot de passe incorrect!\r\n");
				valid_command = 1;
			}
			if(!strncmp(msg,"/kick=",6))
			{
				if(me->admin)
				{
					int i;
					char *pseudo = &msg[6];
					int trouve = 0;
					
					pseudo[255+8] = '\0';	// on limite le pseudo a 255 caracteres
					for(i=0;(pseudo[i]!='\0') && (pseudo[i]!='\r') && (pseudo[i]!='\n') && (pseudo[i]!='\t');i++);
					pseudo[i] = '\0';	// on isole le pseudo

					/* on cherche si le pseudo existe */
					pthread_mutex_lock(&mutex);	// debut de la section critique
					for(i=0;i<first_free;i++)
					{
						if(!strcmp(pseudo,clients[i]->pseudo))
						{
							trouve = 1;
							if(!clients[i]->admin)
							{
								pthread_t th_id = clients[i]->id;
								s_client *client = clients[i];
								pthread_mutex_unlock(&mutex);
								send_msg(client->sock,"Vous etes kicke par un admin\r\n");
								client_quit(client,"Kicke par un admin");	
								pthread_cancel(th_id);	// termine le thread correspondant
								send_msg(me->sock,"Le client a ete kicke!\r\n");
								break;
							}
							else send_msg(me->sock,"Impossible de kicker un admin!\r\n");
						}
					}
					pthread_mutex_unlock(&mutex);	// fin de la section critique
					
					if(!trouve)
						send_msg(me->sock,"Impossible de trouver le pseudo!\r\n");
				}
				else send_msg(me->sock,"Cette commande necessite les droits administrateur!\r\n");
				valid_command = 1;
			}
			if(!strncmp(msg,"/?",2))
			{
				send_msg(me->sock,HELP_MSG);
				valid_command = 1;
			}
				
			if(!valid_command)	// commande invalide
				send_msg(sck,"Commande non valide!\r\n");
		}
		else			// message normal
		{	snprintf(msg_to_send,8192,"%s : %s",me->pseudo,msg);
			msg_to_send[8192] = '\0';
			send_all(msg_to_send,me->sock);
		}
	}
	
	return NULL;
}

/* fonction principale */
int main(int argc, char **argv)
{
	int server,sck;
	pthread_t th_id;
	
	printf(BANNER);
	
	server = create_server(PORT);
	while(1)
	{
		sck = server_accept(server,0);
		if(sck == INVALID_SOCKET)
		{
			printf("\nErreur de accept()!\n");
			exit(-1);
		}
		if(nb_clients < MAX_CLIENTS)
		{
			pthread_create(&th_id,NULL,interact,(void *)&sck);
			nb_clients++;
			printf("Nouveau client! %d clients\n",nb_clients);
		}
		else close(sck);
	}
	
	return 0;
}

Conclusion :


Voila je crois que le code n est pas trop complique a comprendre. Si vous avez des questions je serais bien sur ravis d y repondre! Et si vous avez des bugs a signaler, aussi!

Codes Sources

A voir également

Ajouter un commentaire Commentaires
coucou747 Messages postés 12303 Date d'inscription mardi 10 février 2004 Statut Membre Dernière intervention 30 juillet 2012 44
28 août 2004 à 12:57
Mandrake 10.0 ?
Je croyais que xmms ne fonctionnait pas sur cet os ??? Bon, ça devait être spécifique a ma carte son lol...
Mais c'ets quoi ton pc pour survivre a 2 konqueror + xmms + 6 consoles dnas 1 terminal + un autre terminal + kwrite et tt ça dans un suel bureau .....? les 5 autres bureaux ont l'air d'être aussi pleins...
pas mal le système... j'ai esayé ton serveru sur un navigateur web... bon, ça marche pas, mais fallait s'y attendre, en telnet c'est parfait !
Se compile aussi bien sous mandrake 10.1


enfin voila j'ai mis 10/10
MetalDwarf Messages postés 241 Date d'inscription mardi 29 octobre 2002 Statut Membre Dernière intervention 23 janvier 2006
28 août 2004 à 16:46
Oui ce serveur n est pas concu pour repondre au protocole IRC (trop complique!!) meme si ca doit etre faisable avec du temps...
et oui, mandrake 10.0 mais avec kernel et compilateur recompiles et mis a jour
coucou747 Messages postés 12303 Date d'inscription mardi 10 février 2004 Statut Membre Dernière intervention 30 juillet 2012 44
2 sept. 2004 à 21:21
j'eesayais de m'inspirer de ton code pour le faire sans threads... et j'ai aussi lu le linux mag 41...

j'ai croisé une étrange ressemblance entre les deux codes présentés, évidement, le tien a des threads que le code du linux mag n'a pas, mais c'est les mêmes noms de variables, parfois les mêmes lignes...

bon, je ne suis pas capable de reprendre un code de linux mag pour y ajouterdes threads, mais par contre le repater si...

D'ou viennent ces ressemblances ?
jon1012 Messages postés 1 Date d'inscription samedi 10 mai 2003 Statut Membre Dernière intervention 3 septembre 2004
3 sept. 2004 à 00:44
Salut, j'adore ton code (pris sur linux mag ou pas je m'en fiche lol, t'es un boss ;)) !
Par contre, je cherche comment envoyer une liste des clients connectés (je compte faire un client en gtk/c derriere ton serveur un peu modifié, si tu m'en donne l'autorisation bien sur !), si tu pouvais me mettre sur la piste ce serait génial, je n'y comprend pas grand chose en interactions entre threads et la maniere dont je peux trouver la liste de tous les users connectés...
Enfin voilà...
Si tu veux, je pourrais poster ici le code modifié du serv pour aller avec mon client ainsi que mon client (qui normalement sera portable sous windows) :)

Voili voilà, donc bah si tu pouvai me donner ton autorisation de continuer ce que je fais :) (la license n'etant pas indiquée... si c'est du gpl, n'hésite pas à me le dire lol ;))
Voilà ! En tout cas, très bon code, bravo !

Jonathan
MetalDwarf Messages postés 241 Date d'inscription mardi 29 octobre 2002 Statut Membre Dernière intervention 23 janvier 2006
3 sept. 2004 à 17:46
coucou47> He he oui le code de base des sockets reseaux est celui du Linux Magazine 41. En fait j avais recopie les fonctions create_server() et server_accept() au moment de la parution de ce Linux Magazine et depuis je fais un copier-coller de ces fonctions dans tous mes codes reseaux. Je precise quand meme que ce sont des fonctions de base qui se trouvent un peu partout (comme le fait remarquer l auteur de cet article dans un autre Linux Mag).
Sinon tout le code du serveur en lui meme est evidemment de moi, et en particulier la gestion des threads qui est le point que je voulais etudier en programmant ce serveur.

jom1012> Merci pour le code ca fait plaisir. Pour la liste des clients connectes c est tres simple, c est une commande qui est deja implementee dans le serveur. Si tu regarde bien c est la commande "/list". En fait toute les informations sur les clients sont contenus dans un tableau de pointeur sur une structure qui contient les infos sur le client, dont un champ pseudo (un char *) qui est le pseudo du client en question. Pour obtenir la liste des clients il suffit alors (cote serveur) de lire ces valeurs en parcourant le tableau (apres verrouillage de la structure par le mutex). Si tu veux rajouter des commandes c est tres simple. Il n y a qu une seule question a se poser : est ce que ma commande DOIT acceder (en lecture ou ecriture) au tableau des clients connectes? Si c est le cas il faut prendre le mutex AVANT de manipuler et le relacher APRES, en le gardant le moins de temps possible. D ailleurs le code peut etre ameliore en certains endroits car ceratines fonctions peuvent etre bloquantes sont appeles alors que le mutex est pris mais ce probleme n est pas tres grave. Si tu ne fais que toucher a me (la structure du client), il ne sert a rien de toucher au mutex.
Pour la realisation d un client en C/GTK je suis tout a fait pour, et je voulais le faire mais je ne connais pas le gtk et je n ai pas le temps pour me plonger dedant. Donc bien sur que tu as le droit de reprendre ce que j ai fait si tu mentionnes que le serveur original est de moi et que tu mets ce que tu as faut sur ce site.

Voila si tu as d autres questions je suis la pour y repondre.

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.