Serveur de chat multithreade en c sous linux

Soyez le premier à donner votre avis sur cette source.

Vue 16 834 fois - Téléchargée 1 621 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

Messages postés
9
Date d'inscription
samedi 30 janvier 2010
Statut
Membre
Dernière intervention
2 juillet 2013

super super super ! rien que de voir comment ca marche avec les thread qui sont trop compliqués pour moi(je suis un grand debutant )!
mais pour le client on fait deux thread d envoie et de réception ! ca serait super d en connaitre plus du coté client ! mais je vais éssayer de me casser la tête pour le client ca vais étre compliqué mais je vais prendre comme référence le code serveur !
merci pour le savoir que vous partager ! merci d étre genereux de ton savoir MetalDwarf .
Messages postés
1
Date d'inscription
lundi 10 novembre 2003
Statut
Membre
Dernière intervention
25 septembre 2007

Code super propre!! merci c'est génial

Je digere le code et j'essairai de faire un client en flash.

Merci encore
Messages postés
12303
Date d'inscription
mardi 10 février 2004
Statut
Modérateur
Dernière intervention
30 juillet 2012
36
G 1 truK bi1 pr ke tu puiS nou liR...

On va tous parler corectement ok ? sinon, ça risque de devennir totalement illisible...

tu tapes :
user@linux$ telnet host port
dans une console linux

Moi j'ajoute un message pour remercier MetalDwarf, je lui dois mon augmentation de niveau depuis ces trois dèrnières années...
Messages postés
33
Date d'inscription
lundi 19 juin 2006
Statut
Membre
Dernière intervention
15 novembre 2007

j'ai lancé le source sous linux ,avec la commande " ./server " , mais je ne sais pas ce k'il faus faire par la suite , est ce ke je doit ecrire un autre programme pour ke le chatr fonctionne ???
merci de m'indiquer comment faire marchee ce source client /serveur
Messages postés
3
Date d'inscription
lundi 22 novembre 2004
Statut
Membre
Dernière intervention
1 octobre 2007

Pour ceux que ça interesse, voici un client ligne de commande fonctionnant avec ce serveur de chat:

http://globnet.free.fr/include/m1-client.exe

Il s'agit d'un "simple" programme telnet adapté pour faire en sorte que les messages arrivant ne viennent pas perturber la saisie d'un message en cours.
Afficher les 27 commentaires

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.