From 381abe2ff0e7573488c31e4037f73f5b8f043777 Mon Sep 17 00:00:00 2001 From: SOL Yanis Date: Sat, 11 Nov 2023 23:52:59 +0100 Subject: [PATCH] Soumission J3 --- travail/jalon1/client.c | 4 +- travail/jalon3/Makefile | 14 + travail/jalon3/client.c | 930 ++++++++++++++++++++++ travail/jalon3/common.h | 5 + travail/jalon3/msg_struct.h | 49 ++ travail/jalon3/server.c | 1271 ++++++++++++++++++++++++++++++ travail/{jalon1 => }/travail.txt | 0 7 files changed, 2271 insertions(+), 2 deletions(-) create mode 100644 travail/jalon3/Makefile create mode 100644 travail/jalon3/client.c create mode 100644 travail/jalon3/common.h create mode 100644 travail/jalon3/msg_struct.h create mode 100644 travail/jalon3/server.c rename travail/{jalon1 => }/travail.txt (100%) diff --git a/travail/jalon1/client.c b/travail/jalon1/client.c index c34a2d4..f0a8995 100644 --- a/travail/jalon1/client.c +++ b/travail/jalon1/client.c @@ -116,8 +116,8 @@ int main(int argc, char *argv[]) { printf("\nMessage envoyé. Vous pouvez envoyer un nouveau message :\n"); fflush(stdout); - if (strcmp(buf, "/quit\n") == 0) { - printf("Connexion fermée\n"); + if (strcmp(buf, "/quit\n") == 0) { + printf("Connexion fermée\n"); break; } } diff --git a/travail/jalon3/Makefile b/travail/jalon3/Makefile new file mode 100644 index 0000000..a95bd62 --- /dev/null +++ b/travail/jalon3/Makefile @@ -0,0 +1,14 @@ +CC = gcc +CFLAGS = -Wall -g -Wno-switch + +all: server client + +server: server.c + $(CC) $(CFLAGS) -o server server.c + +client: client.c + $(CC) $(CFLAGS) -o client client.c + +clean: + rm -f server client + diff --git a/travail/jalon3/client.c b/travail/jalon3/client.c new file mode 100644 index 0000000..ccde9cd --- /dev/null +++ b/travail/jalon3/client.c @@ -0,0 +1,930 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "msg_struct.h" + +// Cette fonction valide le format des commandes utilisateur en fonction du type de message. +// Elle vérifie la correspondance de la commande avec le type de message attendu, la présence d'un éventuel pseudonyme et d'un éventuel message, et leurs format et longueur. +// Elle renvoie -1 en cas de format invalide et 0 sinon. +int valid_format(enum msg_type type, char *user_input) { + + switch (type) { + + case NICKNAME_NEW: + + if (strncmp(user_input, "/nick", strlen("/nick")) != 0){ + printf("[Server] : Invalid format. Please use \"/nick \".\n"); + return -1; + } + + if ( (strcmp(user_input, "/nick") == 0) || (strcmp(user_input, "/nick ") == 0) ) { + // L'utilisateur n'a pas renseigné de nickname après "/nick " + printf("[Server] : Nickname not provided. Please use \"/nick \".\n"); + return -1; + } + + if (*(user_input + strlen("/nick")) != ' ') { + printf("[Server] : Please add a space between \"/nick\" and your username.\n"); + return -1; + } + + if (strlen(user_input) - strlen("/nick ") > NICK_LEN) { + printf("[Server] : The provided username exceeds the maximum length of %d characters. Please try again.\n", NICK_LEN); + return -1; + } + + // Vérification que le pseudo ne contient pas un caractère spécial + for (int i = 0; i < strlen(user_input + strlen("/nick ")); i++) { + if (!isalnum( *(user_input + strlen("/nick ") + i) )) { + // Parcours du pseudo : Si un caractère n'est ni un chiffre ni une lettre de l'alphabet, c'est un caractère spécial + printf("[Server] : Invalid nickname. Please try again using only alphanumeric characters without spaces.\n"); + return -1; + } + } + + break; + + case NICKNAME_INFOS: + + if ( (strcmp(user_input, "/whois") == 0) || (strcmp(user_input, "/whois ") == 0) ) { + // L'utilisateur n'a pas renseigné de nickname après "/whois " + printf("[Server] : Nickname not provided. Please use \"/whois \".\n"); + return -1; + } + + if (*(user_input + strlen("/whois")) != ' ') { + printf("[Server] : Please add a space between \"/whois\" and the username.\n"); + return -1; + } + + if (strlen(user_input) - strlen("/whois ") > NICK_LEN) { + printf("[Server] : The provided username exceeds the maximum length of %d characters. Please try again.\n", NICK_LEN); + return -1; + } + + break; + + case NICKNAME_LIST: + // Commandes acceptées : "/who", "/who ","/who " + if ( strcmp(user_input, "/who") == 0 || strcmp(user_input, "/who ") == 0 || strncmp(user_input, "/who ", strlen("/who ")) == 0 ) { + return 0; + } else { + printf("[Server] : Incorrect format. Please use one the accepted command formats : \"/who\", \"/who \",\"/who \"\n"); + return -1; + } + + break; + + case UNICAST_SEND: + // Cas où l'utilisateur ne fournir aucun argument + if ( (strcmp(user_input, "/msg") == 0) || (strcmp(user_input, "/msg ") == 0) ) { + printf("[Server] : No input arguments. Please use \"/msg \".\n"); + return -1; + } + // Cas où l'utilisateur colle les arguments à la commande + if ( *(user_input + strlen("/msg")) != ' ' ) { + printf("[Server] : A space should be added after \"/msg\". Please use \"/msg \".\n"); + return -1; + } + + // Contrôle de taille + // Recherche de l'espace séparant le pseudo du message + int idx_input = strlen("/msg "), idx_name = 0; + int target_name_size = 0; + char name[NICK_LEN]; + while (user_input[idx_input] != ' ' && user_input[idx_input] != '\0') { + name[idx_name] = user_input[idx_input]; + target_name_size += 1; + idx_input += 1; + idx_name += 1; + } + + // Cas de la donnée d'une chaîne sans aucun espace après "/msg " + if (target_name_size > NICK_LEN){ + printf("[Server] : The provided nickname exceeds the maximum length of %d characters. Please try again.\n", INFOS_LEN); + return -1; + } + + if (user_input[idx_input] == ' ' && user_input[idx_input+1] == '\0'){ + idx_input += 1; // Pour passer au message après l'espace + } + + else if (user_input[idx_input] == '\0' || user_input[idx_input+1] == '\0'){ // Cas de l'absence de message + printf("[Server] : No message provided. Please use \"/msg \".\n"); + return -1; + } + // Contrôle de taille du message + if (strlen(user_input) - strlen("/msg ") - strlen(name) > INFOS_LEN) { + printf("[Server] : The provided message exceeds the maximum length of %d characters. Please try again.\n", INFOS_LEN); + return -1; + } + + break; + + case BROADCAST_SEND: + + if ( (strcmp(user_input, "/msgall") == 0) || (strcmp(user_input, "/msgall ") == 0) ) { + // L'utilisateur n'a pas renseigné de message + printf("[Server] : Message not provided. Please use \"/msgall \".\n"); + return -1; + } + + if (*(user_input + strlen("/msgall")) != ' ') { + printf("[Server] : Please add a space between \"/msgall\" and the message.\n"); + return -1; + } + + if (strlen(user_input) - strlen("/msgall ") > INFOS_LEN) { + printf("[Server] : The provided message exceeds the maximum length of %d characters. Please try again.\n", INFOS_LEN); + return -1; + } + + break; + + + ///////////////////////////////////////////////////////////////////// + ////////////////// JALON 3 : MULTICAST ////////////////////////// + + case MULTICAST_CREATE: + if ( (strcmp(user_input, "/create") == 0) || (strcmp(user_input, "/create ") == 0) ) { + // L'utilisateur n'a pas renseigné de nickname après "/create " + printf("[Server] : Channel name not provided. Please use \"/create \".\n"); + return -1; + } + + if (*(user_input + strlen("/create")) != ' ') { + // L'utilisateur n'a pas inséré d'espace après "/create" + printf("[Server] : Please add a space between \"/create\" and the channel name.\n"); + return -1; + } + + if (strlen(user_input) - strlen("/create ") > CHANNEL_NAME_LEN) { + // L'utilisateur a renseigné un nom de salon trop long + printf("[Server] : The provided channel name exceeds the maximum length of %d characters. Please try again.\n", CHANNEL_NAME_LEN); + return -1; + } + + break; + + // Vérification que le channel_name ne contient pas un caractère spécial + for (int i = 0; i < strlen(user_input + strlen("/create ")); i++) { + if (!isalnum( *(user_input + strlen("/create ") + i) )) { + // Parcours du channel_name : si un caractère n'est ni un chiffre ni une lettre de l'alphabet, c'est un caractère spécial + printf("[Server] : Invalid channel name. Please try again using only alphanumeric characters without spaces.\n"); + return -1; + } + } + + + case MULTICAST_LIST: + // Commandes acceptées : "/channel_list", "/channel_list ", "/channel_list " + if ( strcmp(user_input, "/channel_list") == 0 || + strcmp(user_input, "/channel_list ") == 0 || + strncmp(user_input, "/channel_list ", strlen("/channel_list ")) == 0 ) { + return 0; + } else { + printf("[Server] : Incorrect format. Please use one the accepted command formats : \"/channel_list\", \"/channel_list \",\"/channel_list \"\n"); + return -1; + } + + break; + + + + case MULTICAST_JOIN: + + if ( (strcmp(user_input, "/join") == 0) || (strcmp(user_input, "/join ") == 0) ) { + // L'utilisateur n'a pas renseigné de salon après "/join " + printf("[Server] : Channel name not provided. Please use \"/join \".\n"); + return -1; + } + + if (*(user_input + strlen("/join")) != ' ') { + // L'utilisateur n'a pas inséré d'espace après "/join" + printf("[Server] : Please add a space between \"/join\" and the channel name.\n"); + return -1; + } + + if (strlen(user_input) - strlen("/join ") > CHANNEL_NAME_LEN) { + // L'utilisateur a renseigné un nom de salon trop long + printf("[Server] : The provided channel name exceeds the maximum length of %d characters. Please try again.\n", CHANNEL_NAME_LEN); + return -1; + } + + // Vérification que le channel_name ne contient pas un caractère spécial + for (int i = 0; i < strlen(user_input + strlen("/join ")); i++) { + if (!isalnum( *(user_input + strlen("/join ") + i) )) { + // Parcours du channel_name : si un caractère n'est ni un chiffre ni une lettre de l'alphabet, c'est un caractère spécial + printf("[Server] : Invalid channel name. Please try again using only alphanumeric characters without spaces.\n"); + return -1; + } + } + + break; + + + + case MULTICAST_SEND: + if (strlen(user_input) > INFOS_LEN) { + printf("[Server] : The provided message exceeds the maximum length of %d characters. Please try again.\n", INFOS_LEN); + return -1; + } + break; + + + case MULTICAST_QUIT: + + /* Tests implicitement vérifiés dans prepare_message normalement + + if ( (strcmp(user_input, "/quit") == 0) || (strcmp(user_input, "/quit ") == 0) ) { + // L'utilisateur n'a pas renseigné de nom de salon après "/quit " + printf("[Server] : Channel name not provided. Please use \"/join \".\n"); + return -1; + } + + + + if (*(user_input + strlen("/quit")) != ' ') { + // L'utilisateur n'a pas inséré d'espace après "/quit" + printf("[Server] : Please add a space between \"/quit\" and the channel name.\n"); + return -1; + } + + *///////////// + + + if (strlen(user_input) - strlen("/quit ") > CHANNEL_NAME_LEN) { + // L'utilisateur a renseigné un nom de salon trop long + printf("[Server] : The provided channel name exceeds the maximum length of %d characters. Please try again.\n", CHANNEL_NAME_LEN); + return -1; + } + break; + + + // Vérification que le channel_name ne contient pas un caractère spécial + for (int i = 0; i < strlen(user_input + strlen("/create ")); i++) { + if (!isalnum( *(user_input + strlen("/create ") + i) )) { + // Parcours du channel_name : si un caractère n'est ni un chiffre ni une lettre de l'alphabet, c'est un caractère spécial + printf("[Server] : Invalid channel name. Please try again using only alphanumeric characters without spaces.\n"); + return -1; + } + } + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + + + + } + + + return 0; // Format et longueur valides +} + + +// Cette fonction prend le type de message en paramètre et renvoie la sous-chaîne correspondante !!!AVEC L'ESPACE!!!. +// Cela rend modulaire la préparation du payload dans prepare_message. +const char* get_command_string(enum msg_type type) { + switch (type) { + case NICKNAME_NEW: + return "/nick "; + case BROADCAST_SEND: + return "/msgall "; + case UNICAST_SEND: + return "/msg "; + case NICKNAME_INFOS: + return "/whois "; + case NICKNAME_LIST: + return "/who "; + default: + return ""; // Pour le cas ECHO_SEND où il n'y a pas de préfixe + } +} + + +// La fonction prepare_message remplit une structure message en fonction de la commande utilisateur et donc du type de message. +// Elle utilise la fonction valid_format pour effectuer des vérifications préliminaires sur le format de la commande. Ensuite, elle remplit le payload et la structure message, les rendant prêts à l'envoi. +// Elle renvoie -1 en cas de format invalide, -2 en cas d'erreur et 0 en cas de succès. +int prepare_message(struct message *msg, char *user_input, const char *nick_sender, char *payload, char *my_channel, int in_channel) { + + msg->infos[0] = '\0'; // Rempli si besoin + enum msg_type type = 0; + int ret = -2; + + if (strncmp(user_input, "/nick", strlen("/nick")) == 0) + type = NICKNAME_NEW; + + else if (strncmp(user_input, "/msgall", strlen("/msgall")) == 0) + type = BROADCAST_SEND; + + else if (strncmp(user_input, "/msg", strlen("/msg")) == 0) + type = UNICAST_SEND; + + else if (strncmp(user_input, "/whois", strlen("/whois")) == 0) + type = NICKNAME_INFOS; + + + else if (strncmp(user_input, "/who", strlen("/who")) == 0) + type = NICKNAME_LIST; + + + ///////////////////////////////////////////////////////////////////// + ////////////////// JALON 3 : MULTICAST ////////////////////////// + + else if (strncmp(user_input, "/create", strlen("/create")) == 0) + type = MULTICAST_CREATE; + + else if (strncmp(user_input, "/channel_list", strlen("/channel_list")) == 0) + type = MULTICAST_LIST; + + else if (strncmp(user_input, "/join", strlen("/join")) == 0) + type = MULTICAST_JOIN; + + else if ( strncmp(user_input, "/quit", strlen("/quit")) == 0 || strncmp(user_input, "/quit ", strlen("/quit ")) == 0 ) {// FAIRE LA DIFF ENTRE "/quit " ET "/quit" + if ( strcmp(user_input, "/quit") == 0 || strcmp(user_input, "/quit ") == 0 ) + type = ECHO_SEND; + else if (strlen(user_input) > strlen("/quit ") && in_channel == 1) + if (strcmp(user_input+strlen("/quit "), my_channel) == 0 ) + type = MULTICAST_QUIT; + } + + else { + if (in_channel == 1) + type = MULTICAST_SEND; + else + type = ECHO_SEND; + } + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + + + + + ret = valid_format(type, user_input); + if (ret == -1) { + return -1; + } + + strcpy(payload, user_input + strlen(get_command_string(type))); + + // Remplissage de la structure msg + msg->type = type; + strcpy(msg->nick_sender, nick_sender); + msg->pld_len = strlen(payload); + + if (type == NICKNAME_NEW || // Tous les types dont le champ infos est non vide. + type == NICKNAME_INFOS || + type == UNICAST_SEND || + type == MULTICAST_CREATE || + type == MULTICAST_JOIN || + type == MULTICAST_QUIT ) { + + strncpy(msg->infos, payload, INFOS_LEN); + } + + else if (type == MULTICAST_SEND) + strncpy(msg->infos, my_channel, INFOS_LEN); + return 0; // Format valide, structure msg remplie et payload stocké +} + + + + +// Cette fonction permet de gérer un envoi à un serveur en deux temps : un premier message contenant la struct message suivi préremplie d'un second contenant les octets utiles. +// Elle renseigne aussi sur le statut de l'envoi en retournant "0" en cas de succès et "-1" en cas d'erreur. +int send2steps(int fd, struct message msg, const char *payload) { + int sent = 0; + while (sent != sizeof(struct message)) { + // Envoi de la structure dans un premier temps + int ret = write(fd, (char *)&msg + sent, sizeof(struct message) - sent); + if (ret == -1) { + return -1; // Erreur d'écriture + } + + sent += ret; + } + + if (msg.type != NICKNAME_LIST) { + // Envoi du payload + sent = 0; + while (sent != msg.pld_len) { + int ret = write(fd, payload + sent, msg.pld_len - sent); + if (ret == -1) { + return -1; + } + + sent += ret; + } + } + + return 0; // succès +} + +// Cette fonction permet de gérer la réception d'envoi par un serveur en deux temps : La réception de la struct message suivie des octets utiles. +// Elle renseigne aussi sur le statut de la lecture en retournant "0" en cas de succès, "-1" en cas d'erreur de lecture et "-2" en cas d'erreur lors de l'allocation dynamique de mémoire. +int receive2steps(int fd, struct message *msg, char **payload) { + + int received = 0; + + while (received != sizeof(struct message)) { + // Réception de la structure dans un premier temps + int ret = read(fd, (char *)msg + received, sizeof(struct message) - received); + if (ret == -1) + return -1; // Erreur de lecture + + received += ret; + } + + int payload_size = msg->pld_len; + + *payload = (char *)malloc(payload_size); + if (*payload == NULL) { + return -2; // Pour différencier du retour d'erreur du read + } + memset(*payload, 0, payload_size); + + (*payload)[payload_size] = '\0'; + + received = 0; + while (received != payload_size) { + // Réception des données du champ infos + int ret = read(fd, *payload + received, payload_size - received); + if (ret == -1){ + free(*payload); + return -1; // Erreur de lecture + } + received += ret; + } + + return 0; // succès +} + + +// Cette fonction gère l'attribution initiale du pseudo pour un nouvel utilisateur. +// Elle utilise une boucle de vérification du format en appelant la fonction prepare_message puis envoie la demande de création de pseudonyme au serveur, qui répond avec un flag (0 en cas de succès, 1 si le pseudonyme renseigné est déjà utilisé) +// Elle renvoie -1 en cas d'erreur, 0 en cas de succès. +int initialNickname(int fd, char **my_nickname) { + char user_input[512]; // buffer qui sert à remplir le champ msg.infos au 1er envoi + user_input[0] = '\0'; + + *my_nickname = (char *)malloc(NICK_LEN); + if (*my_nickname == NULL) { + perror("malloc"); + return -1; + } + + struct message msg; + memset(&msg, 0, sizeof(struct message)); + + printf("[Server] : please login with /nick \n"); + fflush(stdout); + + while (1) { + + memset(user_input, 0, strlen(user_input)); + + if (fgets(user_input, sizeof(user_input), stdin) == NULL) { + perror("fgets"); + free(my_nickname); + exit(EXIT_FAILURE); + } + + user_input[strlen(user_input)-1]='\0'; // retrait du \n + + if (strncmp(user_input, "/nick", strlen("/nick")) != 0){ + printf("[Server] : Invalid format, please login with : /nick \n"); + continue; + } + + if ( (strcmp(user_input, "/nick") == 0) || (strcmp(user_input, "/nick ") == 0) ) { + // L'utilisateur n'a pas renseigné de nickname après "/nick " + printf("[Server] : Nickname not provided, use \"/nick \".\n"); + continue; + } + + if (*(user_input + strlen("/nick")) != ' ') { + printf("[Server] : Please add a space between \"/nick\" and your username.\n"); + continue; + } + + if (strlen(user_input) - strlen("/nick ") > NICK_LEN) { + printf("[Server] : Username is too long. Please use a username with a maximum length of %d characters.\n", NICK_LEN); + continue; + } + + int special_character_detected = 0; + // Vérification que le pseudo ne contient pas un caractère spécial + for (int i = 0; i < strlen(user_input + strlen("/nick ")); i++) { + if (!isalnum( *(user_input + strlen("/nick ") + i) )) { + // Parcours du pseudo : Si un caractère n'est ni un chiffre ni une lettre de l'alphabet, c'est un caractère spécial + special_character_detected = 1; + printf("[Server] : Invalid nickname. Please try again using only alphanumeric characters without spaces.\n"); + break; + } + } + if (special_character_detected) + continue; + + // Remplissage de la structure msg + msg.type = NICKNAME_NEW; + msg.nick_sender[0] = '\0'; + msg.pld_len = strlen(user_input + strlen("/nick ")); + strcpy(msg.infos, user_input + strlen("/nick ")); + + // Envoi du pseudo + int ret = send2steps(fd, msg, user_input + strlen("/nick ")); + if (ret == -1) { + return -1; // Erreur d'envoi + } + char *payload = NULL; + + int res = receive2steps(fd, &msg, &payload); + if (res == -1) { + return -1; + } + if (strcmp(payload, "0") == 0) { + strcpy(*my_nickname, user_input + strlen("/nick ")); + printf("[Server] : Welcome to the chat %s !\n", *my_nickname); + break; + } + else if (strcmp(payload, "1") == 0) + printf("[Server] : Nickname already taken, please try again.\n"); + + free(payload); + } + return 0; +} + + + + + + + + + + + + + + +int main(int argc, char *argv[]) { + + if (argc != 3) { + fprintf(stderr, "Merci de rentrer deux arguments : \n"); + exit(EXIT_FAILURE); + } + + struct addrinfo hints, *result; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + int ret = getaddrinfo(argv[1], argv[2], &hints, &result); // renvoie 0 si succès + if (ret != 0) { + perror("getaddrinfo"); + freeaddrinfo(result); + exit(EXIT_FAILURE); + } + + int fd = socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (fd == -1) { + perror("socket"); + freeaddrinfo(result); + exit(EXIT_FAILURE); + } + + ret = connect(fd, result->ai_addr, result->ai_addrlen); + if (ret == -1) { + perror("connect"); + close(fd); + freeaddrinfo(result); + exit(EXIT_FAILURE); + } + + int received = 0; + char buf_flag[1]; + while (received != sizeof(char)) { + // Réception de la structure dans un premier temps + int ret = read(fd, buf_flag + received, sizeof(char) - received); + if (ret == -1) { + perror("read"); + close(fd); + exit(EXIT_FAILURE); // Erreur de lecture + } + received += ret; + } + printf("Connecting to server ... done!\n"); + + + char *my_nickname = NULL; // stocke le pseudo de l'utilisateur + int nicknew_count = 0; // compteur utilisé pour distinguer le choix initial du pseudo des changements de pseudos + + // Choix du pseudo avant le début de la boucle principale + ret = initialNickname(fd, &my_nickname); + if (ret == -1) { + perror("write"); + close(fd); + free(my_nickname); + exit(EXIT_FAILURE); + } + + + + + + ///////////////////////////////////////////////////////////////////// + ////////////////// JALON 3 : MULTICAST ////////////////////////// + char *my_channel = NULL; // stocke le nom du salon de discussion + my_channel = (char *)malloc(CHANNEL_NAME_LEN); + if (my_channel == NULL) { + perror("malloc"); + close(fd); + free(my_nickname); + exit(EXIT_FAILURE); + } + + int in_channel = 0; // 1 si l'utilisateur est dans un salon, 0 sinon + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + + + + + + struct pollfd fds[2]; // Un descr. pour l'entrée, l'autre (fd) pour la socket + memset(fds, 0, 2*sizeof(struct pollfd)); + fds[0].fd = 0; // stdin + fds[0].events = POLLIN; + fds[0].revents = 0; + fds[1].fd = fd; // socket + fds[1].events = POLLIN; + fds[1].revents = 0; + + + + //printf("%s> ", my_nickname); // Affichage du pseudo de l'utilisateur avant chaque commande + //fflush(stdout); + + while (1) { + + // Affichage du pseudo et salon de l'utilisateur avant chaque commande + if (in_channel == 1){ + printf("%s[%s]> ", my_nickname, my_channel); + fflush(stdout); + } + else { + printf("%s> ", my_nickname); + fflush(stdout); + } + + int ret = poll(fds, 2, -1); // Call poll that awaits new events + + if (ret == -1) { + perror("poll"); + break; + } + + char input_buffer[256]; + + if (fds[0].revents & POLLIN) { // Activité sur stdin + // Lire stdin et envoyer au serveur + if (fgets(input_buffer, sizeof(input_buffer), stdin) == NULL) { + perror("fgets"); + break; + } + input_buffer[strlen(input_buffer)-1]='\0'; // retrait du \n + + /************** Fonctionnalité supplémentaire : l'utilisateur peut connaître son username*******/ + /* via la commande /me */ + if (strcmp(input_buffer, "/me") == 0 || strcmp(input_buffer, "/me ") == 0) { + printf("[Server] : You are : %s\n", my_nickname); + continue; + } + /***********************************************************************************************/ + + struct message msg; + char payload[INFOS_LEN]; + + // Remplissage msg + int res = prepare_message(&msg, input_buffer, my_nickname, payload, my_channel, in_channel); + + if (res == 0) { + send2steps(fds[1].fd, msg, payload); + } + + // Cas d'une demande de déconnexion. + if ( strcmp(payload, "/quit") == 0 || strcmp(payload, "/quit ") == 0 ) { + printf("[Server] : Request successful. You have been disconnected.\n"); + break; + } + + fds[0].revents = 0; + } + + + if (fds[1].revents & POLLIN) { // Activité sur la socket (message du serveur) + // Lire le message du serveur + + struct message msg; + memset(&msg, 0, sizeof(struct message)); + + char *payload = NULL; + + int res = receive2steps(fds[1].fd, &msg, &payload); + if (res == -1) { + perror("read"); + close(fds[1].fd); + free(my_nickname); + exit(EXIT_FAILURE); + } + + int type = msg.type; + switch (type) { + + case NICKNAME_NEW: + if (strcmp(payload, "0") == 0) { + nicknew_count += 1; + + if (nicknew_count >= 1){ // Pour différencier le premier choix de pseudo et les changements ultérieurs + strcpy(my_nickname, input_buffer); + printf("[Server] : Nickname successfully changed to %s !\n", my_nickname + strlen("/nick ")); + } + } + else if (strcmp(payload, "1") == 0) + printf("[Server] : Nickname already taken, please try again.\n"); + break; + + case NICKNAME_LIST: + + char list_nickname[NICK_LEN*MAX_CONN + 5]; + strcpy(list_nickname, "[Server] : Online users are"); + + int idx_payload = 0, idx_name = 0; + char name_nickname[NICK_LEN]; + while (payload[idx_payload] != '.') { + strcat(list_nickname, "\n - "); + + memset(name_nickname, 0, sizeof(name_nickname)); + idx_name = 0; + while( payload[idx_payload] != ',' && payload[idx_payload] != '.' ) { + name_nickname[idx_name] = payload[idx_payload]; + idx_name+=1; + idx_payload+=1; + } + strcat(list_nickname, name_nickname); + + // Pour afficher son propre pseudo + if (strcmp(name_nickname, my_nickname) == 0) + strcat(list_nickname, " (me)"); + + // Pour passer à la position après le séparateur ", " + if (payload[idx_payload] == ',') + idx_payload += 2; + } + printf("%s\n", list_nickname); + + break; + + case NICKNAME_INFOS: + printf("%s\n", payload); + break; + + case UNICAST_SEND: + if (strlen(payload) > 1) { + printf("%s\n", payload); + } + break; + + case BROADCAST_SEND: + printf("%s\n", payload); + break; + + case ECHO_SEND: + printf("%s\n", payload); + break; + + ///////////////////////////////////////////////////////////////////// + ////////////////// JALON 3 : MULTICAST ////////////////////////// + + case MULTICAST_CREATE: + + if (strcmp(payload, "0") == 0) { + printf("You have created channel %s\n", my_channel + strlen("/create ")); + strcpy(my_channel, input_buffer); + printf("You have joined %s\n", my_channel + strlen("/create ")); + } + else if (strcmp(payload, "1") == 0) + printf("Channel name already taken, please try again.\n"); + else if (strcmp(payload, "2") == 0) + printf("Server has reached maximum channel capacity. Please try again.\n"); + break; + + case MULTICAST_LIST: + if (payload[0] == '\0') + printf("No channels available.\n"); + else { + char list_channel[CHANNEL_NAME_LEN*MAX_CHANNELS + 14]; + strcpy(list_channel, "List of created channels :"); + + idx_payload = 0, idx_name = 0; + char name_channel[CHANNEL_NAME_LEN]; + while (payload[idx_payload] != '.') { + strcat(list_channel, "\n - "); + + memset(name_channel, 0, sizeof(name_channel)); + idx_name = 0; + while( payload[idx_payload] != ',' && payload[idx_payload] != '.' ) { + name_channel[idx_name] = payload[idx_payload]; + idx_name+=1; + idx_payload+=1; + } + strcat(list_channel, name_channel); + if (strcmp(name_channel, my_nickname) == 0) + strcat(list_channel, " (<--)"); + + + + // Pour passer à la position après le séparateur ", " + if (payload[idx_payload] == ',') + idx_payload += 2; + } + printf("%s\n", list_channel); + } + + break; + + case MULTICAST_JOIN: + if (strcmp(msg.nick_sender, my_nickname) == 0) + { + if (strcmp(payload, "0") == 0) { + strcpy(my_channel, input_buffer); + in_channel = 1; + printf("INFO> You have joined %s\n", my_channel + strlen("/join ")); + } + else if (strcmp(payload, "1") == 0) ///////// à revoir avec serveur + printf("INFO> You are already in this channel.\n"); + else if (strcmp(payload, "2") == 0) + printf("INFO> This channel does not exist. Please try again\n"); + else if (strcmp(payload, "3") == 0) + printf("INFO> Channel has reached maximum number of users. Please try again.\n"); + } + + else + printf("INFO> %s has joined %s\n", msg.nick_sender, my_channel + strlen("/join ")); + break; + + case MULTICAST_SEND: + if (strlen(payload) > 1) { + printf("%s> : %s\n", msg.nick_sender, payload); + } + break; + + case MULTICAST_QUIT: + if (strcmp(msg.nick_sender, my_nickname) == 0) { + + if (strcmp(payload, "oh gosh") == 0) + printf("You were the last user in this channel, %s has been destroyed\n", my_channel); + + else if (strcmp(payload, "you're so big") == 0) + printf("INFO> You have left %s\n", my_channel); + + // Réinitialisation + in_channel = 0; + my_channel[0] = '\0'; + } + + else + printf("INFO> : %s has quit %s\n", msg.nick_sender, my_channel); + + break; + + ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////// + + } + + free(payload); + fds[1].revents = 0; + } + + } + + free(my_nickname); + free(my_channel); + freeaddrinfo(result); + close (fds[1].fd); + printf("Have a nice day :)\n"); + return 0; +} diff --git a/travail/jalon3/common.h b/travail/jalon3/common.h new file mode 100644 index 0000000..e1f1137 --- /dev/null +++ b/travail/jalon3/common.h @@ -0,0 +1,5 @@ +#define MSG_LEN 1024 +#define SERV_PORT 8080 +#define SERV_ADDR "127.0.0.1" +#define MAX_CONN 256 // le serveur pourra accepter un maximum de 255 connexions +#define MAX_CHANNELS 10 // Le nombre de salons pouvant exister simultanément diff --git a/travail/jalon3/msg_struct.h b/travail/jalon3/msg_struct.h new file mode 100644 index 0000000..d6cf3fa --- /dev/null +++ b/travail/jalon3/msg_struct.h @@ -0,0 +1,49 @@ +#define NICK_LEN 20 +#define INFOS_LEN 128 +#define CHANNEL_NAME_LEN 30 + +enum msg_type { + NICKNAME_NEW, + NICKNAME_LIST, + NICKNAME_INFOS, + ECHO_SEND, + UNICAST_SEND, + BROADCAST_SEND, + MULTICAST_CREATE, + MULTICAST_LIST, + MULTICAST_JOIN, + MULTICAST_SEND, + MULTICAST_QUIT, + FILE_REQUEST, + FILE_ACCEPT, + FILE_REJECT, + FILE_SEND, + FILE_ACK +}; + +struct message { + int pld_len; + char nick_sender[NICK_LEN]; + enum msg_type type; + char infos[INFOS_LEN]; +}; + +static char* msg_type_str[] = { + "NICKNAME_NEW", + "NICKNAME_LIST", + "NICKNAME_INFOS", + "ECHO_SEND", + "UNICAST_SEND", + "BROADCAST_SEND", + "MULTICAST_CREATE", + "MULTICAST_LIST", + "MULTICAST_JOIN", + "MULTICAST_SEND", + "MULTICAST_QUIT", + "FILE_REQUEST", + "FILE_ACCEPT", + "FILE_REJECT", + "FILE_SEND", + "FILE_ACK" +}; + diff --git a/travail/jalon3/server.c b/travail/jalon3/server.c new file mode 100644 index 0000000..8bc69e7 --- /dev/null +++ b/travail/jalon3/server.c @@ -0,0 +1,1271 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "msg_struct.h" + +#define MAX_CHANNELS 10 // Le nombre de salons pouvant exister simultanément +#define MAX_CLIENTS 100 // Le nombre maximum d'utilisateurs appartenant à un salon + + +// ============================ Fonctions de gestion de la liste chaînée stockant les informations des clients connectés ============================ + +// Cette structure Client permet de representer les information d'un client connecté +typedef struct Client { + char pseudo[NICK_LEN]; // Pseudonyme du client + int fd; // Descripteur de fichier de la socket client + char address[INET_ADDRSTRLEN]; // Adresse IP du client (stockée sous forme de chaîne de caractère lisible) + int port; // Numéro de port utilisé par le client + char connection_time[30]; // Date de connexion du client + char channel[CHANNEL_NAME_LEN] = {'\0'}; // Nom du salon auquel le client appartient; cette chaîne est vide quand le client n'appartient pas à un salon + struct Client* next; // Pointeur vers le client suivant dans la liste +} Client; + + + +// Cette fonction permet de créer une nouvelle structure CLient +Client* create_Client(char* client_pseudo, int client_fd, struct sockaddr_in client_addr, char* client_connection_time) { + Client* new_Client = (Client*)malloc(sizeof(Client)); + // Contrôle d'erreur + if (new_Client == NULL) + return NULL; + + // Remplissage avec les informations du client + strcpy(new_Client->pseudo, client_pseudo); + new_Client->fd = client_fd; + inet_ntop(AF_INET, &(client_addr.sin_addr), new_Client->address, INET_ADDRSTRLEN); // conversion de l'adresse IP en une chaîne lisible + new_Client->port = ntohs(client_addr.sin_port); + strcpy(new_Client->connection_time, client_connection_time); + new_Client->next = NULL; + + return new_Client; + } + + + +// Cette fonction permet d'ajouter un Client à la chaîne +void add_Client(Client** head, int client_fd, struct sockaddr_in client_addr, char* client_connection_time) { + + Client* new_Client = create_Client("NotDefinedYet", client_fd, client_addr, client_connection_time); + // Contrôle d'erreur + if (new_Client == NULL) { + perror("Failed to create a new client structure"); + return; + } + // Parcours de la liste pour trouver le dernier noeud + Client* current = *head; + if (current == NULL){ // si la liste est vide + *head = new_Client; + } + else { + // Recherche du dernier noeud + while (current->next != NULL) { + current = current->next; + } + // Ajout du nouveau CLient + current->next = new_Client; + } + // Le nouveau Client est le dernier élément de la liste + new_Client->next = NULL; +} + + + +// Cette fonction permet d'enlever un Client de la liste +void remove_Client(Client** head, int client_fd) { + Client* previous = NULL; + Client* current = *head; + + // Parcours de la liste pour trouver le client à supprimer + while ((current != NULL) && (current->fd != client_fd)) { + previous = current; + current = current->next; + } + + // Si le Client en question n'existe pas + if (current == NULL) { + return; + } + + // Suppression du Client en question + if (previous == NULL) // Cas où le client est le premier noeud de la liste + *head = current->next; + else + previous->next = current->next; + + // Désallocation de mémoire + free(current); + return; +} + + + +// Cette fonction permet de désallouer la liste chaînée +void free_Clients(Client** head) { + Client* current = *head; + while (current != NULL) { + Client* temp = current; + current = current->next; + free(temp); + } + *head = NULL; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// + + +// ============================ Fonctions de gestion des salons ============================ + + +// Cette structure permet de representer un salon +typedef struct { + char name[CHANNEL_NAME_LEN]; // Nom du salon + Client* members[MAX_CLIENTS] = NULL; // Tableau des clients appartenant au salon + int nb_members; // Nombre d'utilisateurs actuels dans le salon +} Channel; + + + +// Cette fonction permet de créer une nouveau salon +Channel* create_Channel(const char* channel_name) { + Salon* new_Channel = (Channel*)malloc(sizeof(Channel)); + // Contrôle d'erreur + if (new_Channel == NULL) + return NULL; + + // Remplissage des informations du salon + strcpy(new_Channel->name, channel_name); + new_Channel->nb_members = 0; + + return new_Channel; +} + +// Cette fonction permet de gérer la création et l'ajout d'un nouveau salon à la liste des salons +// Elle retourne "0" en cas d'ajout réussi et "-1" en cas d'erreur. +int add_Channel(Channel** head, const char* channel_name) { + + Channel* new_Channel = create_Channel(channel_name); + // Contrôle d'erreur + if (new_Channel == NULL) { + perror("Failed to create a new client structure"); + return -1; + } + // Parcours de la liste pour trouver le dernier noeud + Channel* current = *head; + if (current == NULL){ // si la liste est vide + *head = new_Channel; + } + else { + // Recherche du dernier noeud + while (current->next != NULL) { + current = current->next; + } + // Ajout du nouveau salon + current->next = new_Channel; + } + // Le nouveau salon est le dernier élément de la liste + new_Channel->next = NULL; + return 0; +} + + + +// Cette fonction permet d'enlever un salon de la liste des salons +void remove_Channel(Channel** head, const char* channel_name) { + Channel* previous = NULL; + Channel* current = *head; + + // Parcours de la liste pour trouver le channel à supprimer + while ((current != NULL) && (current->name != channel_name)) { + previous = current; + current = current->next; + } + + // Si le salon en question n'existe pas + if (current == NULL) { + return; + } + + // Suppression du salon en question + if (previous == NULL) // Cas où le salon est le premier noeud de la liste + *head = current->next; + else + previous->next = current->next; + + // Désallocation de mémoire + free(current); + return; +} + + +111 commentaire a mettre +int add_to_channel(Channel** head_channels, Client* client_structure, int client_socket, char* channel_name, + Channel* joined_channel){ + + Channel* current_channel = *head_channels; + // Parcous de la liste des salons pour trouver le salon à rejoindre + while ( current_ channel != NULL && strcmp(current_channel->name, channel_name)!=0 ) { + current_channel = current_channel->next + } + + if (current_channel == NULL) // Cas où le salon à rejoiundre n'existe + return -1; + else if (current_channel->nb_members == MAX_CLIENTS) // Cas où le salon est plein + return -2; + + // Le salon existe : La fonction renseigne la structure du salon à rejoindre + joined_channel = current_channel; + + // Parcours du tableau des membres du salon pour vérifier que le client n'appartient pas déjà au salon en question + for (int i = 0; i < MAX_CLIENTS; i++){ + if (current_channel->members[i]->fd == client_socket) + return -3; + } + + // Parcours du tableau des membres pour trouver une case vide pour l'ajout du client au salon + for (int i = 0; i < MAX_CLIENTS; i++){ + + if (current_channel->members[i] == NULL){ // Case vide trouvée + // Ajout du client au salon demande + client_structure->channel = current_channel->name; + current_channels->members[i] = client_structure; + current_channel->nb_members += 1; + return 0; + } + + } +} + + +commentaire a completer +// Cette fonction permet de retirer un membre d'un salon +int remove_from_channel(Channel** head, int client_socket, const char* channel_name, + Channel* quit_channel) { + Channel* current = *head; + // Parcours de la liste pour trouver le salon à supprimer + while ((current != NULL) && (current->name != channel_name)) { + current = current->next; + } + + + // Parcours et suppression du client de la table des membres du salon en question + for (int i = 0; i < MAX_CLIENTS; i++){ + if (current->members[i]->fd == client_socket){ + quit_channel = current; + current->nb_members -= 1; + free(current->members[i]; + current->members[i] = NULL; + break; + } + } + // Désallocation de mémoire + free(current); + return 0; +} + + + +// Cette fonction permet de désallouer la liste chaînée des salons +void free_Channels(Channel** head) { + Channel* current = *head; + while (current != NULL) { + Channel* temp = current; + current = current->next; + free(temp); + } + *head = NULL; +} + +// A définir dans le main : head_channels et nb_salon +!!!!!!! + + +////////////////////////////////////////////////////////////////////////////////////////////// + + +// ============================ Fonctions de gestion au niveau de la socket d'écoute / fonction d'envoi ============================ + +// Cette fonction permet de gérer la configuration de la socket d'écoute du serveur, le remplissage des informations d'adressage, ainsi que l'association de cette adresse à la socket d'écoute. +// Elle retourne 0 en cas de succès et -1 en cas d'erreur +int handle_bind(int listen_fd, struct sockaddr_in* listening_addr, int server_port) { + // Configuration de la socket d'écoute + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); + + // Informations d'adressage + listening_addr->sin_family = AF_INET; + listening_addr->sin_port = htons(server_port); + inet_aton("0.0.0.0", &listening_addr->sin_addr); + + // Association d'adresse à la socket + int ret = bind(listen_fd, (struct sockaddr *)listening_addr, sizeof(struct sockaddr_in)); + // Contrôle d'erreur + if (ret == -1) + return -1; + return 0; +} + + + +// Cette fonction permet de gérer l'envoi d'un flag à un client pour le notifier d'un événement, la signification de ce flag étant définie en accord entre le serveur et le cient, et explicitée au cas par cas dans ce programme. +// Elle retourne 0 en cas de succès et -1 en cas d'erreur +int handle_flag_send(int client_socket, char* flag){ + int flag_size = sizeof(char); + int sent = 0; + while (sent!= flag_size) { + int ret = write(client_socket, flag+sent, flag_size-sent); + if (ret == -1) + return -1; + sent += ret; + } + return 0; +} + + + +// Cette fonction permet de gérer l'ajout d'une nouvelle socket client au tableau des struct pollfd tout en notifiant le client de l'état de sa demande de connexion +// Elle retourne "0" en cas d'ajout réussi, "-2" en cas de surcharge du serveur et "-1" en cas d'erreur. +int add_client_to_poll(struct pollfd* fds, int new_fd){ + // Ajout de la socket client dans un case vide du tableau + for (int i=1; i < MAX_CONN; i++){ + if( fds[i].fd == 0 ){ + fds[i].fd = new_fd; + fds[i].events = POLLIN | POLLOUT; // Surveiller pour les événements de read et write + fds[i].revents = 0; + // Notification du client que sa demande de connexion a abouti : Envoi du flag "0" + int ret = handle_flag_send(new_fd, "0"); + // Contrôle d'erreur + if (ret == -1) + return -1; + return 0; + } + } + // Surcharge du serveur : Notification du client que sa demande de connexion n'a pas abouti : Envoi du flag "1" + int ret = handle_flag_send(new_fd, "1"); + // Contrôle d'erreur + if (ret == -1) + return -1; + + return -2; +} + + + +// Cette fonction peret d'obtenir l'horodatage de connexion d'un nouveau client au format "AAAA/MM/JJ @ h:min" +void get_connection_time(char* formatted_time){ + // Obtention de la date actuelle + time_t current_time_raw; + time(¤t_time_raw); + // Conversion du temps actuel en structure struct tm pour personnaliser ensuite l'affichage via ses champs + struct tm time_info; + localtime_r(¤t_time_raw, &time_info); + // Conversion de l'horodatage en une chaîne de caractères lisible au format demandé + strftime(formatted_time, 20, "%Y-%m-%d @ %H:%M", &time_info); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// + + +// ============================ Fonctions d'envoi et de réception ============================ + +// Cette fonction permet de gérer un envoi à un client en deux temps : Un premier message contenant la struct message suivi préremplie d'un second contenant les octets utiles. +// Elle renseigne aussi sur le statut de l'envoi en retournant "0" en cas de succès et "-1" en cas d'erreur. +int handle_send(int client_socket, struct message response_structure, char* payload) { + + // Premier envoi : la struct message + int to_send = sizeof(response_structure); + int sent = 0; + while(sent != to_send){ + int ret = write(client_socket, (char*)&response_structure+sent, to_send-sent); + // Contrôle d'erreur + if(ret == -1) + return -1; + sent += ret; + } + + //Deuxième envoi : Les octets utiles + to_send = response_structure.pld_len; + sent = 0; + while(sent != to_send){ + int ret = write(client_socket, payload+sent, to_send-sent); + if(ret == -1) + return -1; + sent += ret; + } + printf("\n\nPAYLOAD ACTUALLY SENT / %s\n\n", payload); + + return 0; +} + + + +// Cette fonction permet de gérer la réception d'envoi par un client en deux temps : La réception de la struct message suivie des octets utiles. +// Elle renseigne aussi sur le statut de la lecture en retournant "0" en cas de succès, "-1" en cas d'erreur de lecture et "-2" en cas d'erreur lors de l'allocation dynamique de mémoire +int handle_receive(int client_socket, struct message* received_structure, char** payload) { + + // Première lecture : la struct message + int to_read = sizeof(struct message); + int received = 0; + while(received != to_read){ + int ret = read(client_socket, (char*)received_structure+received, to_read-received); + // Contrôle d'erreur + if(ret == -1) + return -1; + received += ret; + } + + // Il n'y a pas de payload envoyé par le client pour le type NICKNAME_LIST + if (received_structure->type == NICKNAME_LIST) + return 0; + + //Deuxième lecture : les octets utiles + int payload_size = received_structure->pld_len; + printf("\nChamp pld_len recu dans handle_receive : %d\n", payload_size); + // En se basant sur le taille du payload maintenant connue, on alloue dynamiquement de la mémoire pour le payload + *payload = (char*)malloc(payload_size+1); + if (*payload == NULL) + return -2; + memset(*payload, 0, payload_size); + + (*payload)[payload_size] = '\0'; + printf("Payload après le memset de handle_receive : %s\n", *payload); + received = 0; + while(received != payload_size){ + int ret = read(client_socket, *payload+received, payload_size-received); + if(ret == -1){ + free(*payload); + return -1; + } + received += ret; + } + + printf("Payload rempli par handle_receive : %s\n", *payload); + printf("Taille du payload recu dans handle_receive : %d\n", (int)strlen(*payload)); + return 0; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// + + +// ============================ Fonctions de gestion des demandes utilisateur selon le type du message ============================ + + +// Cette fonction permet de vérifier si un peudo a déjà été choisi par un client. +// Elle renvoie 1 si le peudo en question existe déjà et 0 s'il n'existe pas. +int pseudo_exists(Client** head, const char* client_pseudo) { + Client* current = *head; + + while (current != NULL) { // Parcours de la liste chaînée + // Cas où le pseudo est déjà attribué + if (strcmp(current->pseudo, client_pseudo) == 0) { + return 1; + } + current = current->next; + } + + return 0; +} + + + +// Cette fonction permet de vérifier si un nom de salon a déjà été choisi par un client. +// Elle renvoie 1 si le nom en question existe déjà et 0 s'il n'existe pas. +int channel_name_exists(Channel** head, const char* channel_name) { + Channel* current = *head; + + while (current != NULL) { // Parcours de la liste chaînée + // Cas où le nom est déjà attribué + if (strcmp(current->name, channel_name) == 0) { + return 1; + } + current = current->next; + } + + return 0; +} + + + +// Cette fonction permet de vérifier dans le cas de la demande de définition ou de changement de pseudonyme par un client (avec la commande /nick , type NICKNAME_NEW) que le pseudonyme est valable, auquel cas il sera stocké dans la structure contenant ses informations. +// Le contrôle d'erreur sur le pseudonyme renseigné est fait en utilisant un flag pour signaler si une erreur est survenue ainsi que sa nature. +// La fonction retoune 0 en cas de succès et -1 en cas d'erreur +int handle_nickname_request(Client** head, char* client_pseudo, int client_socket, struct message response_struct){ + // Cas où le pseudo existe déjà : envoi du flag "1" dans le payload au client + if (pseudo_exists(head, client_pseudo) == 1) { + response_struct.pld_len = strlen("1"); + strcpy(response_struct.infos, "1"); + int ret = handle_send(client_socket, response_struct, "1"); + if (ret == -1) + return -1; + return 0; + } else { + // Cas où le pseudo est valable : envoi du flag "0" dans le payload au client + response_struct.pld_len = strlen("0"); + strcpy(response_struct.infos, "0"); + int ret = handle_send(client_socket, response_struct, "0"); + if (ret == -1) + return -1; + // Enregistrement du pseudo après parcours de la liste chaînée + Client* current = *head; + int first_pseudo_flag = 0; + while (current != NULL) { + if (current->fd == client_socket) { + if (strcmp(current->pseudo, "NotDefinedYet") == 0) + first_pseudo_flag = 1; + strcpy(current->pseudo, client_pseudo); + + // Affichage de l'information de connexion une fois que le nouveau client a choisi son pseudo + if (first_pseudo_flag == 1) + printf("User %s connected\n", current->pseudo); + } + current = current->next; + } + + return 0; + } + } + + + +// Cette fonction permet de traiter la demande de rejoindre un salon par un client (avec la commande /join , type MULTICAST_JOIN). +// Elle commence par vérifier que le salon en question existe bien, que le salon n'est pas plein, puis que le client n'appartient pas déjà à ce salon. Si ces conditions sont vérifiées, le client est ajouté au salon en quttant automatiquement le salon auquel le client appartient déjà, le cas échéant. +// Le contrôle d'erreur avec le client est fait en utilisant un flag qui lui sera renvoyé pour signaler si la demande a été traitée avec succès, ou si une erreur est survenue ainsi que sa nature. Les autres membres du salon sont notifiés de l'arrivée du nouveau client. +// La fonction retoune 0 en cas de succès et -1 en cas d'erreur +int handle_join_request(Channel** head_channels, char* channel_name, Client** head_clients, int sender_socket, struct message response_struct){ + + // Pour stocker les informations du client + Channel* client_structure = NULL; + char client_pseudo[NICK_LEN]; + // Pour stocker les informations sur le salon à quitter + char old_channel_name[MAX_CHANNEL_LEN]; + + + // Parcours de la liste des clients pour trouver la structure du client et en extraire les informations dont le nom du salon à quitter notamment + Client* current_client = *head_clients; + while (current_client != NULL){ + if (current_client->fd == sender_socket){ + client_structure = current_client; + strcpy(client_pseudo, current_client->pseudo); + strcpy(old_channel_name, current_client->channel); + } + current_client = current_client->next; + } + + // Pointeur vers la structure du salon à rejoindre, qui sera renseigné par la fonction add_to_channel + Channel* new_channel = NULL; + + int ret = add_to_channel(head_channels, client_structure, sender_socket, channel_name, new_channel); + + // Cas où le salon à rejoindre n'existe pas : envoi du flag "2" dans le payload au client + if (ret == -1){ + response_struct.pld_len = strlen("2"); + strcpy(response_struct.infos, "2"); + int ret = handle_send(client_socket, response_struct, "2"); + if (ret == -1) + return -1; + return 0; + } + // Cas où le salon est plein : Envoi du flag ¨3" + else if (ret == -2){ + response_struct.pld_len = strlen("3"); + strcpy(response_struct.infos, "3"); + ret = handle_send(sender_socket, response_struct, "3"); + if (ret == -1) + return -1; + return 0; + } + // Cas où le cient appartient déjà au salon en question : Envoi du flag ¨1" + else if (ret == -3){ + response_struct.pld_len = strlen("1"); + strcpy(response_struct.infos, "1"); + ret = handle_send(sender_socket, response_struct, "1"); + if (ret == -1) + return -1; + return 0; + } + + // Cas du succès : Notification du client qu'il a rejoint le salon : Enovi du flag "0" + response_struct.pld_len = strlen("0"); + strcpy(response_struct.infos, "0"); + ret = handle_send(sender_socket, response_struct, "0"); + if (ret == -1) + return -1; + + // Notifications des autres membres du salon de l'arrivée du nouveau membre + for (int i = 0; i < MAX_CLIENTS; i++){ + // Exclusion du nouveau membre, identifié par le numéro du descripteur de sa socket + if (new_channel->members[i]->fd == sender_socket) + continue; + + // Envoi du message avec l'entête "[channel_name]> INFO> " qui sera affiché tel quel par les destinataires + char payload_to_send[strlen(current_channel->name)+10 + strlen(sender_pseudo)+strlen(" has joined ")+]strlen(current_channel->name)]; + strcpy(payload_to_send, "["); + strcat(payload_to_send, current_channel->name); + strcat(payload_to_send, "]> INFO> "); + strcat(payload_to_send, sender_pseudo); + strcat(payload_to_send, " has joined "); + strcat(payload_to_send, current_channel->name); + strcat(payload_to_send, "\n"); + + response_struct.pld_len = strlen(payload_to_send); + + int ret = handle_send(new_channel->members[i].fd, response_struct, payload_to_send); + if (ret == -1) + return -1; + } + + // Sortie de l'ancien salon et notification des autres membres du salon de la sortie + if (strlen(old_channel_name) > 0){ + // Pointeur vers la structure du salon à quitter, qui sera renseigné par la fonction remove_from_channel + Channel* old_channel = NULL; + + // Suppression du client du tableau des membres du salon à quitter + remove_from_channel(head_channels, sender_socket, old_channel_name, old_channel); + + // Notifications des autres membres du salon de départ + for (int i = 0; i < MAX_CLIENTS; i++){ + // Envoi du message avec l'entête "[channel_name]> INFO> " qui sera affiché tel quel par les destinataires + char payload_to_send[strlen(old_channel_name)+10 + strlen(sender_pseudo)+strlen(" has quit ")+]strlen(old_channel_name)]; + strcpy(payload_to_send, "["); + strcat(payload_to_send, old_channel_name); + strcat(payload_to_send, "]> INFO> "); + strcat(payload_to_send, sender_pseudo); + strcat(payload_to_send, " has quit "); + strcat(payload_to_send, old_channel_name); + strcat(payload_to_send, "\n") + + response_struct.pld_len = strlen(payload_to_send); + int ret = handle_send(old_channel->members[i].fd, response_struct, payload_to_send); + if (ret == -1) + return -1; + } + + } + return 0; +} + + + +// Cette fonction permet de vérifier dans le cas de la demande de création de salon par un client (avec la commande /create , type MULTICAST_CREATE) que le serveur n'est pas surchargé, que le nom du salon est valable (non déjà attribué), auquel cas le salon sera créé et ajouté dans la liste des salons. +// Le contrôle d'erreur avec le client sur le nom de salon renseigné est fait en utilisant un flag qui sera renvoyé au client pour signaler le succès de la création ou si une erreur est survenue ainsi que sa nature. +// La fonction retoune 0 en cas de succès et -1 en cas d'erreur +int handle_create_request(Channel** head, Client** head_clients, int nb_channels, char* channel_name, int client_socket, struct message response_struct){ + // Cas où le serveur est surchargé : envoi du flag "2" dans le payload du client + if (nb_channels == MAX_CHANNELS){ + response_struct.pld_len = strlen("2"); + strcpy(response_struct.infos, "2"); + int ret = handle_send(client_socket, response_struct, "2"); + if (ret == -1) + return -1; + return 0; + } + + // Cas où le nom de salon est déjà attribué : envoi du flag "1" dans le payload au client + if (channel_name_exists(head, channel_name) == 1) { + response_struct.pld_len = strlen("1"); + strcpy(response_struct.infos, "1"); + int ret = handle_send(client_socket, response_struct, "1"); + if (ret == -1) + return -1; + return 0; + } else { + // Cas où le nom n'est pas attribué : création et ajout du client à la liste des salons + int ret = add_Channel(head, channel_name); + if (ret == -1) + return -1; + nb_channels += 1; + + Client* client_structure = NULL; + // Recherche de la structure du client et ajout du client au salon + Client* current_client = *head_clients; + while (current_client != NULL ){ + if (current_client->fd === client_socket) + client_structure = current_client; + current_client = current_client->next; + } + + add_to_channel(head, client_structure, client_socket, channel_name, NULL); + + // Notification du client que le salon a été créé et qu'il l'a automatiquement rejoins : Envoi du flag "0" + response_struct.pld_len = strlen("0"); + strcpy(response_struct.infos, "0"); + ret = handle_send(client_socket, response_struct, "0"); + if (ret == -1) + return -1; + + return 0; + } +} + + + +// Cette fonction permet de gérer la demande d'obtention des informations sur un utilisateur en particulier par un client (avec la commande /whois, type NICKNAME_INFOS), en lui envoyant sa date de connexion, son adresse IP et le numéro de port utilisé dans une chaîne de caractères. +// Si le client dont les informations sont demandées n'existe pas, un message approprié est envoyé. +// La fonction retourne 0 en cas de succès et -1 en cas d'erreur +int handle_info_request(Client** head, const char* target_pseudo, int seeking_client_socket, struct message response_struct) { + char response[INFOS_LEN]; + memset(response, 0, sizeof(response)); + // Parcours de la liste chaînée pour trouver le client cherché + Client* current = *head; + strcpy(response, "[Server] : "); + while (current != NULL) { + // Cas où le client en question est trouvé + if (strcmp(current->pseudo, target_pseudo) == 0) { + // Récupération et formattage des information demandées + strcat(response, current->pseudo); + strcat(response, " connected since "); + strcat(response, current->connection_time); + strcat(response, " with IP address "); + strcat(response, current->address); + strcat(response, " and port number "); + char port_str[16]; + sprintf(port_str, "%d", current->port); // Conversion du numéro de port en une chaîne de caractères + strcat(response, port_str); + strcat(response, "\n"); + + // Envoi au client + response_struct.pld_len = strlen(response); + strcpy(response_struct.infos, response); + + int ret = handle_send(seeking_client_socket, response_struct, response); + if (ret == -1) + return -1; + + return 0; + } + + current = current->next; + } + // Cas où le client n'existe pas : Envoi d'une information pertinente + + strcpy(response, "[Server] : User "); + strcat(response, target_pseudo); + strcat(response, " does not exist\n"); + + response_struct.pld_len = strlen(response); + strcpy(response_struct.infos, response); + + // Envoi au client + response_struct.pld_len = strlen(response); + strcpy(response_struct.infos, response); + + int ret = handle_send(seeking_client_socket, response_struct, response); + if (ret == -1) + return -1; + return 0; +} + + + +// Cette fonction permet de gérer la demande d'obtention de la liste des utilisateurs connectés par un client (avec la commande /who, type NICKNAME_LIST), en lui envoyant tous les pseudo dans une chaîne de caractères avec ", " pour délimiteur. +// Le choix d'une unique chaîne de caractère comme structure de données est basé sur la simplicité de l'envoi ainsi que de la séparation des différents pseudo côté client. +// La fonction retourne 0 en cas de succès et -1 en cas d'erreur +int handle_list_request(Client** head, int client_socket, struct message response_struct) { + // Le buffer contenant la liste des pseudo + char buffer_list[NICK_LEN*MAX_CONN]; + buffer_list[0] = '\0'; + + // Parcours de la liste chaînée des Clients et ajout des pseudo au buffer + Client* current = *head; + while (current != NULL) { + strcat(buffer_list, current->pseudo); + if (current->next != NULL){ + strcat(buffer_list, ", "); // Ajout du délimiteur + } + current = current->next; + } + strcat(buffer_list, "."); + response_struct.pld_len = strlen(buffer_list); + // Envoi au client + int ret = handle_send(client_socket, response_struct, buffer_list); + + return ret; +} + + + +// Cette fonction permet de gérer la demande d'obtention de la liste des salons existants par un client (avec la commande /channel_list, type MULTICAST_LIST), en lui envoyant tous les noms de salons dans une chaîne de caractères avec ", " pour délimiteur. +// Le choix d'une unique chaîne de caractère comme structure de données est basé sur la simplicité de l'envoi ainsi que de la séparation des différents pseudo côté client. +// La fonction retourne 0 en cas de succès et -1 en cas d'erreur +int handle_channel_list_request(Channel** head, int client_socket, struct message response_struct) { + // Le buffer contenant la liste des noms de salons + char buffer_list[CHANNEL_NAME_LEN*MAX_CHANNEL]; + buffer_list[0] = '\0'; + + // Parcours de la liste chaînée des salons et ajout des noms au buffer + Channel* current = *head; + + // Cas où il n'existe aucun salon : Envoi d'une chaîne vide au client + if (current == NULL){ + int ret = handle_send(client_socket, response_struct, buffer_list); + return ret; + } + + + while (current != NULL) { + strcat(buffer_list, current->name); + if (current->next != NULL){ + strcat(buffer_list, ", "); // Ajout du délimiteur + } + current = current->next; + } + strcat(buffer_list, "."); + response_struct.pld_len = strlen(buffer_list); + // Envoi au client + int ret = handle_send(client_socket, response_struct, buffer_list); + + return ret; +} + + + +// Cette fonction permet de gérer la demande d'envoi d'un message en diffusion à tous les autres utilisateurs connectés (avec la commande /msgall, type BROADCAST_SEND). +// La fonction retourne 0 en cas de succès et -1 en cas d'erreur +int handle_broadcast_request(Client** head, int sender_socket, struct message response_struct, char* payload) { + + // Récupération du pseudo du client à l'origine du broadcast + char sender_pseudo[NICK_LEN]; + strcpy(sender_pseudo, response_struct.nick_sender); + + // Cette chaîne sera à afficher telle quelle par les clients destinataires + // Sa taille est choisie de manière adaptée à la taille maximale des messages avec l'entête "[Nickname] : " pour identifier le client à l'origine de l'envoi + char payload_to_send[strlen(sender_pseudo)+5+INFOS_LEN]; + strcpy(payload_to_send, "["); + strcat(payload_to_send, sender_pseudo); + strcat(payload_to_send, "] : "); + strcat(payload_to_send, payload); + + response_struct.pld_len = strlen(sender_pseudo)+5+strlen(payload); + + // Envoi du message en broadcast + Client* current = *head; + while (current != NULL) { + // Parcours de la liste chaînée en sautant le client à l'origine de la demande d'envoi + if (current->fd != sender_socket) { + // Envoi au client + int ret = handle_send(current->fd, response_struct, payload_to_send); + // Indication de l'échec de l'envoi + if (ret == -1) + return -1; + } + + current = current->next; + } + + return 0; +} + + + +// Cette fonction permet de gérer la demande d'envoi d'un message en multicast dans le salon dans lequel le client se trouve (pase de commande, type MULTICAST_SEND). +// La fonction retourne 0 en cas de succès et -1 en cas d'erreur +int handle_channel_send_request(Channel** head, int sender_socket, struct message response_struct, char* payload) { + + // Récupération du pseudo du client à l'origine de l'envoi + char sender_pseudo[NICK_LEN]; + strcpy(sender_pseudo, response_struct.nick_sender); + + // Cette chaîne sera à afficher telle quelle par les clients du salon + // Sa taille est choisie de manière adaptée à la taille maximale des messages avec l'entête "nickname> : " pour identifier le client à l'origine de l'envoi + char payload_to_send[strlen(sender_pseudo)+4+INFOS_LEN]; + strcpy(payload_to_send, sender_pseudo); + strcat(payload_to_send, "> : "); + strcat(payload_to_send, payload); + + response_struct.pld_len = strlen(sender_pseudo)+4+strlen(payload); + + // Parcours de la liste des salons pour trouver celui auquel appartient le client + Channel* current_channel = *head; + while (current_channel != NULL) { + + if (strcmp(current_channel->name, channel_name) == 0) { + // Envoi du message à tous les membres du salon + Client* current_member = NULL; + for (int i = 0; i < current_channel->nb_members; i++){ + current_member = current_channel->members[i]; + int ret = handle_send(current_member->fd, response_struct, payload_to_send); + if (ret == -1) + return -1; + } + } + current_channel = current_channel->next; + } + return 0; +} + + + +// Cette fonction permet de gérer la demande d'envoi d'un message privé d'un utilisateur à un autre, identifié par son pseudo (avec la commande /msg , type UNICAST_SEND), en procédant par la séparation entre le pseudo du destinataire et le message qui lui est destiné avant l'envoi. +// La fonction retourne 0 en cas de succès et -1 en cas d'erreur. +int handle_PM_request(Client** head, char* received_info, int sender_socket, struct message response_struct) { + + char* info_ptr = received_info; + + // Extraction du pseudo du desinataire du payload envoyé par le client à l'origine de la demande d'envoi par la recherche du premier espace dans le payload + char target_pseudo[NICK_LEN]; + int i = 0; + while (*info_ptr != ' ') { + target_pseudo[i] = *info_ptr; + i+=1; + info_ptr+=1; + } + target_pseudo[i] = '\0'; // Ajout du caractère de fin de chaîne + + info_ptr += 1; // Déplacement du pointeur pour que la chaîne info_ptr ne contienne que le vrai message à envoyer + + // Parcours de la liste chaînée pour trouver le pseudo du destinataire + Client* current = *head; + while (current != NULL) { + if (strcmp(current->pseudo, target_pseudo) == 0) { + + //Envoi au destinataire du message avec l'entête "[Nickname] : " qui sera affiché tel quel par le destinataire + char payload_to_send[strlen(response_struct.nick_sender)+5+INFOS_LEN]; + strcpy(payload_to_send, "["); + strcat(payload_to_send, response_struct.nick_sender); + strcat(payload_to_send, "] : "); + strcat(payload_to_send, info_ptr); + + response_struct.pld_len = strlen(response_struct.nick_sender)+5+strlen(info_ptr); + printf("\n\nPAYLOAD TO BE SENT TO %s : %s", target_pseudo, payload_to_send); + printf("SIZE OF PAYLOAD TO BE SENT : %d\n\n", response_struct.pld_len); + + int ret = handle_send(current->fd, response_struct, payload_to_send); + if (ret == -1) + return -1; + + // Notification du client à l'origine de l'envoi que le destinataire existe : Envoi du flag "0" + response_struct.pld_len = strlen("0"); + strcpy(response_struct.infos, "0"); + ret = handle_send(sender_socket, response_struct, "0"); + if (ret == -1) + return -1; + return 0; + } + current = current->next; + } + + // Cas où le client n'existe pas : Envoi d'un message pertinent au client à l'origine de l'envoi + + char response[INFOS_LEN]; + strcpy(response, "[Server] : User "); + strcat(response, target_pseudo); + strcat(response, " does not exist\n"); + + response_struct.pld_len = strlen(response); + strcpy(response_struct.infos, response); + + int ret = handle_send(sender_socket, response_struct, response); + if (ret == -1) + return -1; + + return 0; +} + + + +// Cette fonction permet de traiter la demande de sortie d'un salon par un client (avec la commande /quit, type MULTICAST_QUIT. +// Le contrôle d'erreur avec le client est fait en utilisant un flag qui sera renvoyé au client pour signaler le succès de sa sortie ou si une erreur est survenue. +// La fonction retoune 0 en cas de succès et -1 en cas d'erreur +int handle_quit_request(Channel** head, Client** head_clients, int nb_channels, char* channel_name, int client_socket, struct message response_struct){ + + Channel** head, int client_socket, const char* channel_name, + Channel* quit_channel) + + + + + + + // Cas où le serveur est surchargé : envoi du flag "2" dans le payload du client + if (nb_channels == MAX_CHANNELS){ + response_struct.pld_len = strlen("2"); + strcpy(response_struct.infos, "2"); + int ret = handle_send(client_socket, response_struct, "2"); + if (ret == -1) + return -1; + return 0; + } + + // Cas où le nom de salon est déjà attribué : envoi du flag "1" dans le payload au client + if (channel_name_exists(head, channel_name) == 1) { + response_struct.pld_len = strlen("1"); + strcpy(response_struct.infos, "1"); + int ret = handle_send(client_socket, response_struct, "1"); + if (ret == -1) + return -1; + return 0; + } else { + // Cas où le nom n'est pas attribué : création et ajout du client à la liste des salons + int ret = add_Channel(head, channel_name); + if (ret == -1) + return -1; + nb_channels += 1; + + Client* client_structure = NULL; + // Recherche de la structure du client et ajout du client au salon + Client* current_client = *head_clients; + while (current_client != NULL ){ + if (current_client->fd === client_socket) + client_structure = current_client; + current_client = current_client->next; + } + + add_to_channel(head, client_structure, client_socket, channel_name, NULL); + + // Notification du client que le salon a été créé et qu'il l'a automatiquement rejoins : Envoi du flag "0" + response_struct.pld_len = strlen("0"); + strcpy(response_struct.infos, "0"); + ret = handle_send(client_socket, response_struct, "0"); + if (ret == -1) + return -1; + + return 0; + } +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////// + + +int main(int argc, char* argv[]) { + + if (argc != 2){ + printf("Please use \"%s \" to launch the server.\n", argv[0]); + exit(EXIT_FAILURE); + } + + // Création de la socket d'écoute + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); + // Contrôle d'erreur + if (listen_fd == -1){ + perror("listening socket"); + exit(EXIT_FAILURE); + } + + // Binding de la socket d'écoute + struct sockaddr_in listening_addr; + int ret = handle_bind(listen_fd, &listening_addr, atoi(argv[1])); + //Contrôle d'erreur + if (ret == -1){ + perror("bind"); + close(listen_fd); + exit(EXIT_FAILURE); + } + + // Configuration de la socket en mode écoute + ret = listen(listen_fd, 10); + // Contôle d'erreur + if (ret == -1){ + perror("listen"); + close(listen_fd); + exit(EXIT_FAILURE); + } + + // Initialisation à 0 du tableau de struct pollfd + struct pollfd fds[MAX_CONN]; + memset(fds, 0, MAX_CONN*sizeof(struct pollfd)); //mise à zéro des cases de l'array des struct pollfd + + // Ajout de la socket d'écoute dans la première case + fds[0].fd = listen_fd; + fds[0].events = POLLIN; + fds[0].revents = 0; + + // Pointeur vers la tête de la liste des CLients (premier noeud) + Client* head = NULL; + + printf("Server online - Waiting for a connection\n"); + + while(1){ + // Appel à la fonction poll qui attend indéfiniment une activité au niveau d'une socket du tableau fds + int nb_active_fds = poll(fds, MAX_CONN, -1); + // Contrôle d'erreur - Arrêt du serveur en cas de disfonctionnement de poll + if (nb_active_fds == -1){ + perror("poll"); + break; + } + + + // Vérification de l'activité au niveau de la socket d'écoute + if ( (fds[0].revents & POLLIN) == POLLIN ){ + // Acceptation de la demande de connexion et création d'une socket client associée + struct sockaddr_in client_addr; + socklen_t len = sizeof(client_addr); + int new_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &len); + // Contrôle d'erreur - Passage à l'itération suivante en cas d'erreur pour garder le serveur réactif aux nouvelles demandes de connexion + if (new_fd == -1){ + perror("accept"); + continue; + } + // Ajout de la socket client dans un case vide du tableau et notification du client de l'état de sa demande de connexion + int ret = add_client_to_poll(fds, new_fd); + // Contrôle d'erreur + if (ret == -1){ + perror("write"); + close(new_fd); + } else if (ret == -2){ + perror("server overloaded"); + close(new_fd); + } + // Obtention de la date de connexion du client au format demandé + char formatted_time[20]; + get_connection_time(formatted_time); + // Stockage des informations du nouveau client dans la liste chaînée + add_Client(&head, new_fd, client_addr, formatted_time); + //On indique que la demande de connexion a été traitée + fds[0].revents = 0; + } + + + + // Vérification de l'activité au niveau des sockets clients + for(int i = 1; i < MAX_CONN; i++){ + + if (fds[i].fd != 0){ + // Traitement en cas de données à lire + if( (fds[i].revents & POLLIN) == POLLIN ) { + + // Première lecture : examen du type du message envoyé + struct message received_structure; + memset(&received_structure, 0, sizeof(received_structure)); + + // Variable permettant de stocker le payload + char* payload_data = NULL; + + int ret = handle_receive(fds[i].fd, &received_structure, &payload_data); + // Contrôle d'erreur + if(ret == -1){ + perror("read"); + } else if (ret == -2){ + perror("malloc"); + } + + enum msg_type request_type = 0; + request_type = received_structure.type; + + printf("\n\nA la réception; champ type : %d\n", received_structure.type); + printf("A la réception; champ pld_len : %d\n", received_structure.pld_len); + printf("A la réception; champ infos : %s\n", received_structure.infos); + printf("A la réception; payload : %s\n\n", payload_data); + + // Gestion des demandes de l'utilisateur selon le type du message envoyé + switch (request_type) + { + // Cas d'une demande de définition ou de changement de pseudonyme + case NICKNAME_NEW : + ret = handle_nickname_request(&head, payload_data, fds[i].fd, received_structure); + // Contrôle d'erreur + if (ret == -1) + perror("write"); + break; + // Cas d'une demande de la liste des utilisateurs connectés + case NICKNAME_LIST : + ret = handle_list_request(&head, fds[i].fd, received_structure); + //Contrôle d'erreur + if (ret == -1) + perror("write"); + break; + // Cas d'une demande d'informations sur un utilisateur + case NICKNAME_INFOS : + ret = handle_info_request(&head, payload_data, fds[i].fd, received_structure); + //Contrôle d'erreur + if (ret == -1) + perror("write"); + break; + // Cas d'une demande d'envoi d'un message en diffusion + case BROADCAST_SEND : + ret = handle_broadcast_request(&head, fds[i].fd, received_structure, payload_data); + //Contrôle d'erreur + if (ret == -1) + perror("write"); + break; + // Cas d'une demande d'envoi d'un message privé + case UNICAST_SEND : + ret = handle_PM_request(&head, payload_data, fds[i].fd, received_structure); + //Contrôle d'erreur + if (ret == -1) + perror("write"); + break; + + case ECHO_SEND : + // Cas d'une demande de fermeture de connection + if (strcmp(payload_data, "/quit") == 0){ + // Affichage de l'information de déconnexion + Client* current = head; + while (current != NULL) { + if (current->fd == fds[i].fd) { + printf("User %s disconnected\n", current->pseudo); + } + current = current->next; + } + close(fds[i].fd); + fds[i].fd = 0; + remove_Client(&head, fds[i].fd); + break; + } + + // Cas d'un echo normal + + // Cette chaîne sera à afficher telle quelle par les clients destinataires + char payload_to_send[received_structure.pld_len+strlen("[Server] : ")]; + strcpy(payload_to_send, "[Server] : "); + strcat(payload_to_send, payload_data); + + received_structure.pld_len = received_structure.pld_len + strlen("[Server] : "); + // Renvoi du message + int ret = handle_send(fds[i].fd, received_structure, payload_to_send); + //Contrôle d'erreur + if (ret == -1) + perror("write"); + break; + } + + free(payload_data); + //On indique que l'activité au niveau de la socket a été traitée + fds[i].revents = 0; + } + + // Si la connection a été fermée sans demande explicite de la part de l'utilisateur, on ferme la socket client et on renseigne sa place dans le tableau comme vide. On enlève également le client de la liste chaînée. + if( (fds[i].revents & POLLHUP) == POLLHUP ) { + close(fds[i].fd); + fds[i].fd = 0; + + remove_Client(&head, fds[i].fd); + + // Affichage de l'information de déconnexion + Client* current = head; + while (current != NULL) { + if (current->fd == fds[i].fd) { + printf("User %s disconnected\n", current->pseudo); + } + current = current->next; + } + } + + } + + } + + } + + // Désallocation de la liste chaînée + free_Clients(&head); + // Femeture de la socket du serveur + close(listen_fd); + + printf("Server offline\n"); + + exit(EXIT_SUCCESS); +} + diff --git a/travail/jalon1/travail.txt b/travail/travail.txt similarity index 100% rename from travail/jalon1/travail.txt rename to travail/travail.txt