Ir para o conteúdo

Simples chat de redes em C

Olá a todos, escrevi este meu primeiro artigo para a revista Programar, aqui vai ser demonstrado como é possível escrever um programa que sirva como chat para uma rede. Não irá ser mostrado aqui muita coisa de novo. A ideia deste artigo é demonstrar um método de fazer um pequeno e simples chat em C que possa ser utilizado em redes, poderá servir como boa ferramenta para alguns projectos que alguns leitores da revista Programar tenham. Vou aqui falar principalmente de quatro coisas, manipulação de ficheiros, manipulação de strings, utilizar outros computadores numa rede para se poder gravar e ler ficheiros e um pouco sobre macros. Isto tudo em C e facilmente utilizado em C++. Para conseguir seguir facilmente este artigo o leitor deve ter conhecimento mínimo de redes informáticas (um pouco como eu) e de saber mais do que apenas o básico em C. Vamos começar!

Código de escrita de ficheiro

Primeiro que tudo teremos de escrever um programa que consiga escrever uma mensagem de texto e gravá-la onde quer que seja no computador. Para estes códigos utilizei o Dev-C++ como IDE e o compilador do Dev-C++ também. Aqui veremos o código do que é um programa simples que lê uma mensagem e a guarda num ficheiro, nada de especial aqui:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char msg[300]; //String com trezentos caracteres para a menssagem em questão.
    FILE *f; //Apotador do tipo file que vai guardar o ficheiro com a menssagem.

    gets(msg); //Lemos a menssagem

    f = fopen("ficheiro", "w"); //Abrimos em modo de escrita. Não dei nenhuma estenssão ao ficheiro porque não vai ser preciso lê-lo fora do programa.

    if(f == NULL) //Caso não tenha sido possivel criar um ficheiro ou abrilo para escrita dizemos que ocurreu um erro.
        printf("Ocurreu um erro ao abrir o ficheiro.\n");
    else
    {
        fputs(msg, f); //Escrevemos a menssagem
        fclose(f); //e fechamos o ficheiro.
    }

    return 0;
}

Código de leitura de ficheiro

Depois de termos escrito esta parte do código teremos agora de escrever a parte que abre o ficheiro para o ler e escreve a mensagem no ecrã.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char msg[300]; //String com trezentos caracteres para a menssagem em questão.
    FILE *f; //Apotador do tipo file que vai ler o ficheiro com a menssagem.

    f = fopen("ficheiro", "r"); //Abrimos o mesmo ficheiro em modo de leitura.

    if(f == NULL) //Caso não tenha sido possivel abrir o ficheiro dizemos que ocurreu um erro.
        printf("Ocurreu um erro ao abrir o ficheiro.\n");
    else
    {
        fscanf(f, "%s", msg); //lemos a menssagem do ficheiro
        fclose(f); //e fechamos o ficheiro.
    }

    return 0;
}

Juntando os dois códigos

Com isto tudo feito temos dois bocados de código bastante simples podemos, e devemos se queremos que o programa de chat realmente funcione, por estes dois bocados no mesmo programa, cada um deles num procedimento à parte e ficaríamos com um main da seguinte forma:

int main()
{
    int op;

    while(op != 3)
    {
        printf("Insira opcção\n");
        printf("1- Ler menssagem\n2- Escrever menssagem.\n3- Sair\n");
        scanf("%d", &op);
        if(op == 1)
            ler();
        else if(op == 2)
            escrever();
    }
    return 0;
}

Deixo, obviamente, questões de limpeza de ecrã com o system("cls"), questões do pouco design que é possível fazer com a linha de comandos e outros destes pormenores completamente ao leitor.

Nome do ficheiro

Queria agora falar do nome que o ficheiro vai ter. Na função fopen(), no primeiro parâmetro dizemos qual o nome do ficheiro queremos abrir, mas claro, é também possível dizer qual o caminho no computador que ele vai ter e também qual o caminho na rede. Vou aqui por alguns exemplos do fopen() com aquilo que estou a falar:

f = fopen("ficheiro", "w"); //Este ficheiro vai ficar no directório actual do programa.

f = fopen("c:\\pasta\\outrapasta\\ficheiro", "w"); //Este ficheiro vai ficar no directório C:\pasta\outrapasta.

f = fopen("d:\\ficheiro", "w"); //Este vai ser posto no disco local D.

f = fopen("//nome_do_pc_na_rede/pasta_partilhada/ficheiro", "w"); //Este ficheiro vai ser posto na pasta partilhada num grupo de trabalho em rede.

Nota: Talvez tenham reparado alguns casos onde é apresentada a barra \ duas vezes quando só deveria estar uma vez, isso é porque a barra \ serve para mostrar caracteres especiais em C, tal como o \n e \7 e por aí fora, como a própria barra \ é um caracter especial é preciso por \ para dizer qur vamos inserir um caracter especial e depois \ para pôr o carácter em questão.

Strings

Gostava agora de falar em manipulação de strings, isto será muito importante porque vamos precisar de dizer onde é que queremos gravar o ficheiro com a mensagem e onde é que ele estaria no computador ou/e na rede. Poderíamos por uma pergunta, muito simples no início do programa a dizer “Especifique o directório do ficheiro”, mas isso não teria muita graça, haha, se bem que esta parta seja inteiramente ao critério do leitor. Sugiro que sejam feitas 2 perguntas, 1º Nome do ficheiro, 2º Qual o directório. Depois é só juntar as duas strings usando uma das duas funções:

strcat(directorio_rede, nome_ficheiro);

Esta função vai juntar no fim da variável directório_rede aquilo que está em nome_ficheiro. Podemos utilizar também a função sprintf(), sugiro o uso desta função e já verão porquê.

sprintf(tudo, "%s%s", directorio_rede, nome_ficheiro);

Esta função faz com que o conteúdo da variável tudo seja o que está na variável directorio_rede logo a seguir a nome_ficheiro. Atenção para não por nenhum espaço entre os dois %s porque senão o conteúdo das variáveis não irá estar junta, mas sim com um espaço no meio, também é importante, quando se insere a variável directório_rede, por no final devido à seguinte situação: se inseríssemos no directório c:\pasta e no nome ficheiro, o resultado seria c:\pastaficheiro, por isso é importante pôr no directório c:\pasta\, assim o resultado é c:\pasta\ficheiro, e claro quando o computador for ler a variável com o resultado irá ler c:\pasta\ficheiro e assim tudo funcionará como deve ser.

É importante dizer que neste simples programa é preciso ter em atenção que temos de estar numa rede que permita escrever ficheiros em pastas partilhadas e assim. Talvez para alguns leitores que tenham muito conhecimento sobre rede isso não será problema. Falemos agora da função sprintf(), esta função pode ser utilizada para por qualquer coisa dentro de uma string, seja caracteres, inteiros, reais, mais strings e outros bocados de texto, aqui está o exemplo.

sprintf(var, "Aqui está um inteiro %d e um real %.3f", inteiro, real);

O resultado da string: Aqui está um inteiro 3 e um real 4.256.

Depois deste bocado de código estar inserido teremos um main() da seguinte forma:

int main()
{
    int op;
    char tudo[200], nome[100], directorio[100];

    printf("Insira nome do ficheiro.\n");
    gets(nome);
    printf("Insira directorio.\n");
    gets(directorio);
    sprintf(tudo, "%s%s", directorio, nome);

    while(op != 3)
    {
        printf("Insira opcção\n");
        printf("1- Ler menssagem\n2- Escrever menssagem.\n3- Sair\n");
        scanf("%d", &op);
        if(op == 1)
            ler(tudo);
        else if(op == 2)
            escrever(tudo);
    }
    return 0;
}

Como poderemos ver o programa irá pedir o nome e directório do ficheiro, depois consoante a opção escolhida, 1 ou 2, ele ira iniciar o procedimento ler ou escrever que ira receber por parâmetro a variável tudo, a qual vai conter o caminho do ficheiro na rede. É importante também no fopen() que está nos procedimentos ler() e escrever() mudar o primeiro parâmetro para a variável que o procedimento recebe por parâmetro. Assim, a função fopen() abrirá o ficheiro com o directório que o utilizador escreveu no início do programa.

No final

Se tudo foi bem feito deveremos poder, com o programa aberto obviamente, escrever uma mensagem com a função escrever() e depois ir lê-la com a função ler(). Mas isto é um programa de chat! É necessário estar outro utilizador com outra cópia do programa, noutro computador a fazer exactamente o mesmo. Isso resolve-se, o outro utilizador apenas tem de iniciar o programa e inserir o mesmo nome de ficheiro e mesmo directório que o primeiro utilizador. Põe-se um pequeno problema, isto não será como no Windows Live Menssager, em que podemos escrever várias mensagens que a outra pessoa recebe tudo, é apenas um pequeno sistema em que se escreve uma mensagem para a outra pessoa ir ler, e vice-versa, se escrevermos uma mensagem e depois escrevermos logo outra, então a primeira mensagem irá ser apagada e o outro utilizador não irá receber nada.

Histórico

Vamos agora fazer uma última coisa que será totalmente facultativa. Podemos facilmente acrescentar um histórico, é mais simples do que aprece, apenas gravamos para um ficheiro de texto no directório actual do programa aquilo que recebemos e escrevemos, temos de acrescentar um pequeno procedimento que iremos escrever agora no final dos dois procedimentos ler() e escrever(), este procedimento receberá por parâmetro a mensagem que foi recebida ou escrita e também um inteiro que será 1 ou 2. Este inteiro servirá para “dizer” à função se queremos escrever no histórico se a mensagem foi escrita ou recebida. A função irá pôr a mensagem num ficheiro à frente de uma marca que indicará se fomos nós que escrevemos a mensagem ou o outro utilizador. Esta marca pode ser de várias maneiras, pode ser simplesmente o sinal >> se fomos nos que escrevemos e << se foi o outro utilizador, ou pode também escrever-se outra coisa qualquer, mais uma vez isto é completamente ao critério do leitor. Vejamos como vai ficar o que se chama o procedimento hist():

void hist(char *x, int x)
{
    FILE *f;
    char msg[100];

    f = fopen("hist.txt", "a");

    if(x == 1) //Dentro deste IF se foi o utilizador a escrever a mensagem, no histórico veremos "» Esta é a mensagem"
    {
        sprintf(msg, "> %s\n---", x); //Usamos a função sprintf() para fazer com que a função fique com o conteudo “> Mensagem Aqui (um paragrafo aqui) ---“
        fprintf("%s\n", msg); //Adicionamos mais uma linha com o \n para que o ficheiro fique bem arranjado.
    }
    else if(x == 2)//Dentro deste IF se o utilizador recebeu a mensagem, no histórico veremos "« Esta é a mensagem recebida"
    {
        sprintf(msg, "> %s\\n---", x);
        fprintf("%s\\n", msg);
    }

    fclose(f);
}

Macros

Podemos fazer com que o inteiro seja uma macro, irá facilitar as coisas, para isso basta escrever depois dos #include:

#define ESCRITO 1
#define RECEBIDO 2

Mas o que são macros? As macros são instruções ao pré processador que são sempre indicadas o sinal #. Um exemplo disto é quando no inicio do programa se escreve:

#include <stdio.h>
#include <stdlib.h>
()

O que na verdade estamos a fazer é a incluir dois ficheiros, eles são o stdio.h e o stdlib.h, Se formas à pasta do Dev-C++ podemos ver esses ficheiros dentro da pasta include, podemos também altera-los, mas isso não e aconselhável. Aqueles ficheiros têm as declarações das funções printf(), scanf() e todas as outras que nós utilizamos. As macros não são escritas em C logo não têm de te o ponto e virgula no final. Quase todos os bocados de código em C/C++ podem ser postos em macros, para isso usa-se o #define, aqui está um exemplo:

#define teste printf("Olá\n");

E sempre que a meio do código escrevermos test irá aparcer no ecrã “Olá”. As macros têm inúmeros usos, não os vou explicar todos aqui, claro. Apenas vou só já explicar como é que podemos utilizar as duas macros, que são RECEBIDO e ESCRITO, com a função hist();

hist(msg, ESCRITO);

//OU

hist(msg, 1);

//Irá ser exactamente o mesmo tal como se pusermos:
hist(msg, RECEBIDO);

//OU

hist(msg, 2);

Como já foi dito, o procedimento hist() deve ser chamado no final do procedimento ler() e também no final do procedimento escrever().

Pequeno bug facilmente evitável

Da maneira aqui demonstrada temos o que pode ser um pequeno mas evitável bug, e ele é, se iniciamos o procedimento ler() sem que o outro utilizador ou nós mesmo tenhamos escrito uma nova mensagem o histórico irá ficar com frases repetidas.

Concluindo

Está aqui demonstrado como foi feito um programa que possibilita enviar mensagens entre utilizadores em rede de uma forma silenciosa. Definitivamente este código não deverá ser visto como um programa sozinho mas como um módulo que poderá ser incorporado noutros programas, usem-no bem…