coduri / SocketChatRoom

Chat di Gruppo con Protocollo Over TCP

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Socket Chat Room

Lo scopo è quello di realizzare una chat di gruppo tra più utenti.

Architettura

L'architettura è centralizzata, ogni client invia un messaggio al server e questo si preoccuperà di inoltrarlo a tutti gli altri client connessi.
  • La parte client si connette a un server tramite una socket con connessione TCP. Il client richiede l'inserimento di un nome utente che viene inviato al server e, successivamente, crea due thread, uno per l'invio e l'altro per la ricezione di messaggi.

  • La parte server rimane in ascolto, in attesa che un client si connetta. Quando avviene la connessione, il server, crea un thread che si occupa di ricevere ed inoltrare i messaggi provenienti da quello specifico client.

Per fare il forwarding di un messaggio il server necessita di:

  • Gestire una coda che contiene indirizzo e socket descriptor di tutti i client con cui è attiva una connessione;
  • Un mutex per accedere alla coda in quanto questa è condivisa da tutti i thread;

Protocol Over TCP

Problema del Traffico elevato

Di seguito la prima versione della funzione eseguita dal thread ricevente nel client (link)
void recv_handler(int* pointer_sd) {
    int sd = * pointer_sd;
    char recvbuff[MAXLEN], nomeClient[30];
    int option;

    memset(recvbuff, 0, MAXLEN);

    while(1){
        // Ricevo opzione che mi indica il tipo di messaggio (da server o da altro client)
        recv(sd, &option, sizeof(int), 0);
        int msgType = ntohl(option);

        // Messaggio inviato da un client
        if(msgType == 1){
            recv(sd, nomeClient, 30, 0);    // ricevo nome del mittente
            recv(sd, recvbuff, MAXLEN, 0);  // ricevo messaggio

            printf(COLOR_CYAN);
            printf("%s: ", nomeClient);
            printf(COLOR_RESET);
            printf("%s", recvbuff);
        }

        // Messaggio dal server: indica che c'è stato l'ingresso di un nuovo client nel gruppo
        else if(msgType == 2){
            recv(sd, nomeClient, 30, 0);  // ricevo nome del mittente

            printf(COLOR_GREEN "=== %s si è unito alla chat ===" COLOR_RESET, nomeClient); printf(" \n");
        }

        // Messaggio dal server: indica che c'è stato l'uscita di un client dal gruppo
        else if(msgType == 3){
            recv(sd, nomeClient, 30, 0);  // ricevo nome del mittente

            printf(COLOR_RED "=== %s ha abbandonato ===" COLOR_RESET, nomeClient); printf(" \n");
        }    
    }
}

Si può osservare che il client per stampare un messaggio deve ricevere:

  • Numero che indica la tipologia del messaggio (messaggio normale, join, leave);
  • Nome del client;
  • Contenuto del messaggio (nel caso di messaggio normale);

Se considero una situazione con n client ed un messaggio "normale" da inviare: il server dovrà inviare 3*(n-1) pacchetti, mentre ogni client dovrà ricevere tre pacchetti. Per risolvere questo problema di traffico elevato tra le socket , si è pensato di definire uno pseudo-protocollo over TCP.

Protocollo definito

In generale, un protocollo over TCP definisce come le informazioni sono scambiate tra due o più parti attraverso una connessione TCP, stabilendo un formato standard per i messaggi scambiati e le azioni da intraprendere in risposta a determinati eventi.

Il formato dei messaggi che verranno scambiati tra le parti è formato dai seguenti campi:

  • messageType: consiste in un enum che indica il tipo di messaggio;
  • sender: conterrà il nome del client;
  • message: conterrà il contenuto del messaggio da trasferire (se il messaggio è "normale");
#define MAX_MESSAGE_LENGTH 100
#define MAX_SENDER_NAME_LENGTH 32
#define SIZE_OF_MESSAGE_PROTOCOL sizeof(messageProtocol)

typedef enum {
    MESSAGE_TYPE_JOIN,
    MESSAGE_TYPE_LEAVE,
    MESSAGE_TYPE_SEND,
} messageType;


typedef struct {
    messageType typeMessage;
    char sender[MAX_SENDER_NAME_LENGTH];
    char message[MAX_MESSAGE_LENGTH];
} messageProtocol;

Dimensione del messaggio

Una cosa da prendere in considerazione è che la sizeof di una struttura non è sempre uguale alla somma delle sizeof di ogni membro di essa, questo perché la dimensione della struttura intera include anche le parti di padding aggiunte dal compilatore per allineare i dati in memoria.

Il risultato finale di queste valutazioni mi ha portato a scegliere come dimensione di un singolo messaggio 136byte, scelta che riduce al minimo lo spazio di padding inserito, permettendo comunque la possibilità di trasmettere un numero di caratteri adeguato ad ogni campo.

Analisi mediante debugger Formato pacchetto

About

Chat di Gruppo con Protocollo Over TCP


Languages

Language:C 93.2%Language:CMake 6.8%