Ir para o conteúdo

Chat

Chat em C, com uma abordagem ao estilo "routing" em que todo o trabalho fica do lado do servidor, que faz o encaminhamento das mensagens.

Servidor

/*servidor*/

#include<unistd.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h> //biblioteca pra uso de threads
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<netdb.h>

#define MAX_USERS       100
#define MAX_THREADS     500
#define MAX_BUF         200             /*Limite maximo de dados transaccionaveis em bytes. todos os restantes sao apagados para evitar um possivel overlow*/
#define PROMPT          "P@P Chat -> "
#define FULL            1
#define EMPTY           0
#define SUCCESS         1
#define FAIL            0
#define TRUE            1
#define FALSE           0


/*Variaveis Globais*/


// Tabela de utilizadores para efectuar o routing entre mensagens e clientes
struct RoutingTable                                     /*  -------------------------------------------------*/
{                                                           /*  | UID  |  slot  | Nick      | Cliente_Socket_ID |*/
  int UID[MAX_USERS];                       /*  -------------------------------------------------*/
  int slot[MAX_USERS];                      /*  | 20   | FULL   | apocsantos| 365               |*/
  char Nickname[MAX_USERS][MAX_BUF];    /*  -------------------------------------------------*/
  int ClientSocketID[MAX_USERS];                /*  | 15   | EMPTY  | SarahPeky | 765               |*/
}route;                                                     /*  -------------------------------------------------*/


int GlobalUID = 0;      // id de Utilizador
int usercount = 0;      // numero de utilizadores
int ChatServerSocketID; // id do Socket do Servidor



/* Funcoes*/

/*1) Parse the buffer recieved from client*/
int extract(char * str_rcv,char * msg)
{
   int size_count = 0;                          /*medida preventiva contra buffer overflow*/
   int count = 0;
   char Nickname[MAX_BUF];


      str_rcv += 5; size_count=5;               /*salta o "send" e o espaco*/

      //salta espacos
      while((*str_rcv==' ') && (size_count < MAX_BUF))
      {
         str_rcv++;
         size_count++;
      }

      // grava o nick em memoria
      while((*str_rcv!=' ')&& (size_count < MAX_BUF))
      {
         Nickname[count] = *str_rcv;
         count++;size_count++; str_rcv++;
      }
      Nickname[count]='\0';
      // novamente salta os espacos
      while((*str_rcv==' ') && size_count < MAX_BUF)
      {
         str_rcv++;
         size_count++;
      }

      // grava a mensagem em memoria
      while((*str_rcv!='\0')&& (size_count < MAX_BUF))
      {
         *msg = *str_rcv; msg++; str_rcv++;
         size_count++;
      }
   /*Retira o ClientID*/
   for(count=0;count<MAX_USERS;count++)
   {
     if((!strcmp(route.Nickname[count],Nickname)) && (route.slot[count]==FULL))
       return(route.ClientSocketID[count]);
   }
   return(FAIL);
}


/*2) adiciona um user*/
int add_user(char * str_rcv,int ClientSocketID)
{
  int count=0;
  while(count<MAX_USERS)
  {
     if(route.slot[count]==EMPTY)
     {
        usercount++; GlobalUID++;
        route.UID[count]=GlobalUID;
        strcpy(route.Nickname[count],str_rcv);
        route.slot[count]=FULL;
        route.ClientSocketID[count] = ClientSocketID;
        return(GlobalUID);
     }
     count++;
  }
  return(FAIL);
}


/*3) Remove um user*/
int remove_user(int uid)
{
    int count=0;
    while(count<MAX_USERS)
    {
       if(route.UID[count]==uid)
       {
         route.slot[count]=EMPTY;
         usercount--; // nota: Não é decrementado o GlobalID para evitar duplicacao de dados
         return(SUCCESS);
       }
       count++;
  }
  return(FAIL);
}


/*4) Verifica se o nick não está em uso*/
int isunique(char * str_rcv)
{
  int count=0;
  for(count=0;count<MAX_USERS;count++)
  {
     if((!strcmp(str_rcv,route.Nickname[count])) && (route.slot[count]==FULL))
        return(FAIL);
  }
  return(SUCCESS);
}

void *ChatThread();

/*funcao Main()*/
int main(int argc, char**argv)
{

   struct  sockaddr_in ChatServerAddress;
   pthread_t ChatServerThreadID[MAX_THREADS];
   int count=0;
   int server_running = TRUE;
   char ServerIp[50];
   char ServerPort[50];

   /*Limpa a tabela de routing entre clientes*/
   for(count=0;count<MAX_USERS;count++)
      route.slot[count] = EMPTY;


   memset((void*)&ChatServerAddress,'\0',sizeof(ChatServerAddress));

   /*Obtem o ip e a Porta*/
   printf("Ip do Servidor:");gets(ServerIp);
   printf("Porta:");gets(ServerPort);

   // passa os parametros para a estrutura de sockets
   ChatServerAddress.sin_family = AF_INET;
   ChatServerAddress.sin_port = htons(atoi(ServerPort));
   inet_aton(ServerIp,(&ChatServerAddress.sin_addr));

   /*cria um socket servidor*/
   ChatServerSocketID = socket(AF_INET,SOCK_STREAM,0);
   /*dá um nome ao socket atravez do bind*/
   if(bind(ChatServerSocketID,(struct sockaddr *)&ChatServerAddress,sizeof(ChatServerAddress))<0)
   {
     puts("Erro:Configuração de rede errada.");
     exit(1);
   }

   /*cria uma lista de escuta (queue) com o tamanho MAX_THREADS*/
   listen(ChatServerSocketID,MAX_THREADS);


   /*cria as threads de chat*/
   for(count=0;count<MAX_THREADS;count++)
        pthread_create(&ChatServerThreadID[count],NULL,ChatThread,NULL);

   printf("Servidor levantado e a funcionar em - [%s:%s]\n",ServerIp,ServerPort);

   /*Fecha threads do chat*/
   for(count=0;count<MAX_THREADS;count++)
   {
        pthread_join(ChatServerThreadID[count],NULL);
   }
  printf("Servidor a desligar...[OK]");
  close(ChatServerSocketID);

}



/*Esta funcao faz o grosso do trabalho para permitir clientes ainda mais pequenos*/

void *ChatThread()
{
   char str_rcv[MAX_BUF],str_write_buffer[MAX_BUF];
   char msg[MAX_BUF],nick[MAX_BUF];

   int RouteSocketID, ClientSocketID;
   int ClientUserID;
   int ClientSocketLength;
   int count = 0;
   int isunique_flag=FALSE;
   char ClientIp[INET_ADDRSTRLEN];

   struct sockaddr_in ClientSocketAddress;

   memset((void*)&ClientSocketAddress,'\0',sizeof(ClientSocketAddress));
   ClientSocketLength = sizeof(ClientSocketAddress);
   ClientSocketID = accept(ChatServerSocketID,(struct sockaddr*)&ClientSocketAddress,&ClientSocketLength);

   /*Obtem o Ip do cliente*/
   inet_ntop(AF_INET,(void*)&(ClientSocketAddress.sin_addr),(char*)ClientIp,INET_ADDRSTRLEN);
   /*Inicializa o ecra e informação visivel nele*/
   while(!isunique_flag && strcmp(str_rcv,"exit"))
   {
     memset(str_write_buffer,'\0',MAX_BUF);
     strcpy(str_write_buffer,"\nInsira Nick (ou exit para sair): ");
     write(ClientSocketID,str_write_buffer,MAX_BUF);

     memset(str_rcv,'\0',MAX_BUF);
     read(ClientSocketID,str_rcv,MAX_BUF);
     isunique_flag = isunique(str_rcv);
   }

   if(strcmp(str_rcv,"exit"))
   {

     /*Regista user*/
     if((ClientUserID=add_user(str_rcv,ClientSocketID)))
     {
       strcpy(nick,str_rcv); //mantem o nick

       /*Envia as instruções de uso para os users (tipo welcome dos IRCd)*/
       memset(str_write_buffer,'\0',MAX_BUF);
       // Limpa o ecrao e dá as boas vindas tipo, Benvindo: <nick>
       strcpy(str_write_buffer,"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nBem Vindo ao TPSI-CHAT: ");
       strcat(str_write_buffer,nick);
       strcat(str_write_buffer," [");strcat(str_write_buffer,ClientIp);strcat(str_write_buffer,"]\n");
       strcat(str_write_buffer,"\nComandos:[list], [send <nickname> <msg>], [exit]\n");
       write(ClientSocketID,str_write_buffer,MAX_BUF);
       memset(str_write_buffer,'\0',MAX_BUF);
       strcat(str_write_buffer,"\nLigado : ");
       strcat(str_write_buffer,nick);
       strcat(str_write_buffer," [");strcat(str_write_buffer,ClientIp);strcat(str_write_buffer,"]");
       puts(str_write_buffer);
     }
     else
     {
       memset(str_write_buffer,'\0',MAX_BUF);
       strcpy(str_write_buffer,"\nLamentamos mas o numero de ligacoes maximo foi atingido. Por favor tente novamente mais tarde.\n");
       write(ClientSocketID,str_write_buffer,MAX_BUF);
       strcpy(str_rcv,"exit"); // prepara a saida
     }
    }


   /*inicia a conversacao! (se a entrada for nick(no inicio) e comando a seguir, então sai)*/
   while(strcmp(str_rcv,"exit"))
   {
     /*Apresenta a prompt de dados*/
     memset(str_write_buffer,'\0',MAX_BUF);
     strcpy(str_write_buffer,PROMPT);
     write(ClientSocketID,PROMPT,MAX_BUF);

     /*Aguarda pelo input via standard IO*/
     memset(str_rcv,'\0',MAX_BUF);
     read(ClientSocketID,str_rcv,MAX_BUF);

     /*Activa os comandos - list, send, exit*/

     /*list*/
     if(!strcmp(str_rcv,"list"))
     {
        memset(str_write_buffer,'\0',MAX_BUF);
        strcpy(str_write_buffer,"\nLista de Utilizadoresn");
        write(ClientSocketID,str_write_buffer,MAX_BUF);

        for(count=0;count<MAX_USERS;count++)
        {
           if(route.slot[count] == FULL)
           {
             memset(str_write_buffer,'\0',MAX_BUF);
             strcpy(str_write_buffer,route.Nickname[count]);
             strcat(str_write_buffer,", ");
             write(ClientSocketID,str_write_buffer,MAX_BUF);
           }
        }
     }


    /*send:  Envia a mensagem. Pode causar alguma falha de sincronismo com o ecra mas efectua tentativa de sincronismo de seguida*/
     else if(!strncmp(str_rcv,"send ",5))
     {
        memset(msg,'\0',MAX_BUF);
        RouteSocketID = extract(str_rcv,msg);
        if(RouteSocketID)
        {
          memset(str_write_buffer,'\0',MAX_BUF);
          /*De msg + Continua*/
          strcat(str_write_buffer,"*\n[Msg] ");
          strcat(str_write_buffer,nick);  /*[Msg] <Nickn>: <msg> */
          strcat(str_write_buffer,": ");
          strcat(str_write_buffer,msg);  //escreve a mensagem no buffer
          strcat(str_write_buffer,"\n");
          strcat(str_write_buffer,PROMPT);
          write(RouteSocketID,str_write_buffer,MAX_BUF); //envia a mensagem utilizando o route
        }
        else
        {
          memset(str_write_buffer,'\0',MAX_BUF);
          strcpy(str_write_buffer,"\nO utilizador não está ligado. Use: send <nickn> <msg>");
          write(ClientSocketID,str_write_buffer,MAX_BUF);
        }
     }

     /*sai*/
     else if(!strcmp(str_rcv,"sai"))
     {
        if(remove_user(ClientUserID))
        {
           memset(str_write_buffer,'\0',MAX_BUF);
           strcat(str_write_buffer,"\nDesligado: ");
           strcat(str_write_buffer,nick);
           strcat(str_write_buffer," [");strcat(str_write_buffer,ClientIp);strcat(str_write_buffer,"]");
           puts(str_write_buffer);
           memset(str_write_buffer,'\0',MAX_BUF);
           strcpy(str_write_buffer,"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n Adeus:");
           strcat(str_write_buffer,nick);
           strcat(str_write_buffer," [");strcat(str_write_buffer,ClientIp);strcat(str_write_buffer,"]\n");
           write(ClientSocketID,str_write_buffer,MAX_BUF);
           break;
        }
        else
        {
          memset(str_write_buffer,'\0',MAX_BUF);
          strcpy(str_write_buffer,"\nErro: Caso aconteça por favor envie e-mail para antoniopocsantos@gmail.com com o report");
          write(ClientSocketID,str_write_buffer,MAX_BUF);
        }
     }

/*sendchan:  Envia a mensagem para canal (broadcast). Pode causar alguma falha de sincronismo com o ecra mas efectua tentativa de sincronismo de seguida
     else if(!strncmp(str_rcv,"sendall ",5))
     {
        memset(msg,'\0',MAX_BUF);
        RouteSocketID = extract(str_rcv,msg);
        if(RouteSocketID)
        {
          memset(str_write_buffer,'\0',MAX_BUF);
          //De msg + Continua
          strcat(str_write_buffer,"*\n[Msg] ");
          strcat(str_write_buffer,nick);  //[Msg] <Nickn>: <msg>
          strcat(str_write_buffer,": ");
          strcat(str_write_buffer,msg);
          strcat(str_write_buffer,"\n");
          strcat(str_write_buffer,PROMPT);
          //devia estar aqui o ciclo de envio de mensagem para canal
          for (int i =0; i< MAX_USERS; i++){
          write(RouteSocketID,str_write_buffer,MAX_BUF);
          }
         //terminaria aqui
        }
        else
        {
          memset(str_write_buffer,'\0',MAX_BUF);
          strcpy(str_write_buffer,"\nO cana inexistente. Use: send <nickn> <msg> ou sendch <canal> <mensagem>");
          write(ClientSocketID,str_write_buffer,MAX_BUF);
        }
     }

*/


     /*Comando desconhecido*/
     else
     {
        memset(str_write_buffer,'\0',MAX_BUF);
        strcpy(str_write_buffer,"Comando desconhecido. Digite: list ou send <nick> <msg> ou exit\n ");
        write(ClientSocketID,str_write_buffer,MAX_BUF);
     }
   }
   close(ClientSocketID);
   pthread_exit(NULL);
}

Cliente

/*Cliente de chat*/


#include<unistd.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h> // biblioteca posix de threads
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define MAX_BUF         200 //tamanho maximo do buffer


int ServerSocketID;

char str_ip[MAX_BUF];
char str_rcv[MAX_BUF];


/*Thread para output das mensagens no ecra (modo de texto)*/
void * displaymsg()
{
  while(strcmp(str_ip,"exit"))
  {
    memset(str_rcv,'\0',MAX_BUF);
    read(ServerSocketID,str_rcv,MAX_BUF);
    puts(str_rcv);
  }
  pthread_exit(NULL);
}


/*Thread para receber input do teclado e enviar para o servidor*/
void * inputmsg()
{
  while(strcmp(str_ip,"exit"))
  {
    memset(str_ip,'\0',MAX_BUF);
    gets(str_ip);
    write(ServerSocketID,str_ip,MAX_BUF);
  }
  pthread_exit(NULL);
}


int main() //funcao principal
{

  pthread_t DisplayMsgThreadID, InputMsgThreadID;
  struct sockaddr_in ServerSocketAddress;
  char ServerIp[50]; //ip do servidor
  char ServerPort[50]; //porta do servidor

  /*Obtem Ip e Porta do servidor*/
  memset((void*)&ServerSocketAddress,'\0',sizeof(ServerSocketAddress));
  printf("Server Ip:");gets(ServerIp);
  printf("Server Port:");gets(ServerPort);

  //Preenche os parametros da estrutura de sockets
  ServerSocketAddress.sin_family = AF_INET;
  ServerSocketAddress.sin_port = htons(atoi(ServerPort));
  inet_aton(ServerIp,(&ServerSocketAddress.sin_addr));

  ServerSocketID = socket(AF_INET,SOCK_STREAM,0);

  if(connect(ServerSocketID,(struct sockaddr *)&ServerSocketAddress,sizeof(ServerSocketAddress))<0)
  {
    puts("Erro: Configuracao de rede errada.");
    exit(1);
  }

  pthread_create(&DisplayMsgThreadID,NULL,displaymsg,NULL);
  pthread_create(&InputMsgThreadID,NULL,inputmsg,NULL);

  pthread_join(InputMsgThreadID,NULL);
  pthread_join(DisplayMsgThreadID,NULL);
  printf("Pronto a iniciar conversacao\n");
  close(ServerSocketID);
}