Tutorial - Aplicação em C para transferência de arquivo usando socket TCP e Thread

Aplicação baseada na arquitetura cliente-servidor escrita em C para realizar múltiplas conexões com diversos clientes para transferências de arquivos de qualquer tamanho. É uma ótimo exemplo para conhecer o funcionamento de thread com sockets e recursos em C para pesquisa em diretórios no Linux.

[ Hits: 21.385 ]

Por: Ronaldo Borges em 11/12/2015 | Blog: https://www.facebook.com/ronyjah1


Arquivo servidor, código em C - Thread + socket



O servidor deve ser compilado e executado na máquina na qual ser queria enviar o arquivo. Para executá-lo é necessário dizer porta na qual responderá as requisições do cliente e o diretório de origem dos arquivos.

O servidor é capaz de responder múltiplas conexões. Para tal, foi implementado Threads no lado do servidor.

Para compilar o código C da aplicação servidor copie e salve como server.c em sua máquina o arquivo abaixo e compile da seguinte forma:

gcc -o server server.c -lpthread

Código C do servidor:

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <ctype.h>
#include <pthread.h>

void *connection_handler(void *);

#define MAX_MSG 1024

/*
 Servidor aguarda por mensagem do cliente, imprime na tela
 e depois envia resposta e finaliza processo
 */

int main(int argc, char* argv[]) {

    //Variaveis auxiliares para encontrar o arquivo a ser transferido.
    DIR *mydir;
    struct dirent *myfile;
    struct stat mystat;
    //verificando se foi executando o comando corretamente
    if (argc != 3) {
        fprintf(stderr, "use:./server [Porta] [local]\n");
        return -1;
    } else if (!isdigit(*argv[1])) {
        fprintf(stderr, "Argumento invalido '%s'\n", argv[1]);
        fprintf(stderr, "use:./server [Porta] [local]\n");
        return -1;
    }

    mydir = opendir(argv[2]);
    //verificando se o diretorio existe
    if(mydir == NULL ){fprintf(stderr, "Argumento invalido '%s'\n", argv[2]);return -1;}
    char* aux1 = argv[1];
        int portaServidor = atoi(aux1);

   //variaveis
    int socket_desc, conexao, c, nova_conex;
    struct sockaddr_in servidor, cliente;
    char *mensagem;
    char resposta[MAX_MSG];
    int tamanho, count;

    // para pegar o IP e porta do cliente  
    char *cliente_ip;
    int cliente_port;

    //*********************************************************************//
    //      INICIO DO TRATAMENTO DA THREAD, localização e transferência    // 
    //      do arquivo.                                                    // 
    //*********************************************************************//
    void *connection_handler(void *socket_desc) {
        /*********************************************************/

        /*********comunicao entre cliente/servidor****************/

        // pegando IP e porta do cliente
        cliente_ip = inet_ntoa(cliente.sin_addr);
        cliente_port = ntohs(cliente.sin_port);
        printf("cliente conectou: %s : [ %d ]\n", cliente_ip, cliente_port);

        // lendo dados enviados pelo cliente
        //mensagem 1 recebido nome do arquivo   
        if ((tamanho = read(conexao, resposta, MAX_MSG)) < 0) {
            perror("Erro ao receber dados do cliente: ");
            return NULL;
        }
        resposta[tamanho] = '\0';
        printf("O cliente falou: %s\n", resposta);

        char aux_nomeArquivo[MAX_MSG];
        //fazendo copia do nome do arquivo para variavel auxiliar. tal variavel é utilizada para localizar
        // o arquivo no diretorio.
        strncpy(aux_nomeArquivo, resposta, MAX_MSG);
        //printf("ax_nomeArquivo: %s\n", aux_nomeArquivo);



        /*********************************************************/
        if (mydir != NULL) {

            //funcao busca todo o diretorio buscando o arquivo na variavel aux_nomeArquivo
            //struct stat s;
            while ((myfile = readdir(mydir)) != NULL) {

                stat(myfile->d_name, &mystat);

                printf("Arquivo lido: %s, Arquivo procurado: %s\n", myfile->d_name, resposta);
                if (strcmp(myfile->d_name, resposta) == 0) {//arquivo existe
                   closedir(mydir);
                    //Reiniciando variáveis da pesquisa do diretorio para a proxima thread
                    myfile = NULL;
                    mydir = NULL;
                    mydir = opendir(argv[2]);

                    //**************************************//
                    //      INICIO DO PROTOCOLO            //
                    //*************************************//


                    mensagem = "200";
                    //mensagem 2 - enviando confirmação q arquivo existe
                    write(conexao, mensagem, strlen(mensagem));

                    //mensagem 3 - recebendo que arquivo OK do cliente
                    read(conexao, resposta, MAX_MSG);


                    //**************************************//
                    //      FIM DO PROTOCOLO               //
                    //*************************************//

                    //abrindo o arquivo e retirando o tamanho//
                    //fazendo copia do nome do arquivo para variavel auxiliar. tal variavel é utilizada para localizar
                    // o arquivo no diretorio.


                    char localArquivo[1024]; 
                    strncpy(localArquivo, argv[2], 1024);
                    strcat(localArquivo,aux_nomeArquivo);

                    FILE * f = fopen(localArquivo, "rb");
                    if((fseek(f, 0, SEEK_END))<0){printf("ERRO DURANTE fseek");}
                    int len = (int) ftell(f);                   
                    mensagem = (char*) len;
                    printf("Tamanho do arquivo: %d\n", len);
                    //convertendo o valor do tamanho do arquivo (int) para ser enviado em uma mensagem no scoket(char)
                    char *p, text[32];
                    int a = len;
                    sprintf(text, "%d", len);
                    mensagem = text;

                    //mensagem 4 - enviando o tamanho do arquivo
                    send(conexao, mensagem, strlen(mensagem), 0);

                    int fd = open(localArquivo, O_RDONLY);
                    off_t offset = 0;
                    int sent_bytes = 0;
                    //localArquivo = NULL;
                    if (fd == -1) {
                        fprintf(stderr, "Error opening file --> %s", strerror(errno));

                        exit(EXIT_FAILURE);
                    }

                    while (((sent_bytes = sendfile(conexao, fd, &offset, BUFSIZ)) > 0)&& (len > 0)) {

                        fprintf(stdout, "1. Servidor enviou %d bytes do arquivo, offset é agora : %d e os dados restantes = %d\n", sent_bytes, (int)offset, len);
                        len -= sent_bytes;
                        fprintf(stdout, "2.Servidor enviou %d bytes do arquivo, offset é agora : %d e os dados restantes = %d\n", sent_bytes, (int)offset, len);
                        if (len <= 0) {
                            break;
                        }
                    }
                    //closedir(mydir);
                    while (1) {
                    }

                }
            }if(myfile==NULL) {
                    //enviando mensagem para o cliente de arquivo nao encontrado.
                    mensagem = "404";//file not found
                    printf("\n//*********************************//\n");
                    printf("Arquivo \"%s\" Não Existe no diretório: \"%s\"\n",aux_nomeArquivo, argv[2]);
                    //mensagem 2 - enviando confirmação q arquivo existe
                    write(conexao, mensagem, strlen(mensagem));
                    //sempre que termina de pesquisar o diretorio de arquivos a variavel myfile vai para null
                    // entao eh necessario preencher mydir novamente com o argv[2] com o diretorio de pesquisa. 
                    //caso contrario novas thread nao acessaram o diretorio passado em argv[2]]
                    mydir = opendir(argv[2]);
                    //
                    while (1) {
                    }
                    close(conexao);
                    //closedir(mydir);

            }
            if (mydir != NULL) {
                closedir(mydir);
                mydir = NULL;
            }
        }

        if (strcmp(resposta, "bye\n") == 0) {
            close(conexao);
            printf("Servidor finalizado...\n");
            return NULL;
        }


    }

    //*********************************************************************//
    //      FIM DO TRATAMENTO DA THREAD, localização e transferencia    // 
    //      do arquivo.                                                    // 
    //*********************************************************************//



    //************************************************************
    /*********************************************************/
    //Criando um socket
    socket_desc = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_desc == -1) {
        printf("Nao foi possivel criar o socket\n");
        return -1;
    }

    //Preparando a struct do socket
    servidor.sin_family = AF_INET;
    servidor.sin_addr.s_addr = INADDR_ANY; // Obtem IP do S.O.
    servidor.sin_port = htons(portaServidor);

    //Associando o socket a porta e endereco
    if (bind(socket_desc, (struct sockaddr *) &servidor, sizeof (servidor)) < 0) {
        puts("Erro ao fazer bind Tente outra porta\n");
        return -1;
    }
    puts("Bind efetuado com sucesso\n");

    // Ouvindo por conexoes
    listen(socket_desc, 3);
    /*********************************************************/

    //Aceitando e tratando conexoes

    puts("Aguardando por conexoes...");
    c = sizeof (struct sockaddr_in);

    while ((conexao = accept(socket_desc, (struct sockaddr *) &cliente, (socklen_t*) & c))) {
        if (conexao < 0) {
            perror("Erro ao receber conexao\n");
            return -1;
        }

        pthread_t sniffer_thread;
        nova_conex = (int) malloc(1);
        nova_conex = conexao;

        if (pthread_create(&sniffer_thread, NULL, connection_handler, (void*) nova_conex) < 0) {
            perror("could not create thread");
            return 1;
        }
        puts("Handler assigned");
    }
    if (nova_conex < 0) {
        perror("accept failed");
        return 1;
    }
}

    Próxima página

Páginas do artigo
   1. Arquivo servidor, código em C - Thread + socket
   2. Arquivo cliente, código em C - Transferência do arquivo
Outros artigos deste autor

Tutorial hadoop - Guia prático de um cluster com 3 computadores

Leitura recomendada

BSD Sockets em linguagem C

Monitorando o consumo de banda com Bwbar

Controlando UPLOAD com o CBQ

O Modelo de Referência OSI

Introdução a GTK+ em C

  
Comentários
[1] Comentário enviado por mvforce em 14/12/2015 - 15:50h

Parabéns pelo tutorial... código bem claro e limpo.
Seria bastante didático se você colocasse antes do código fonte uma sequencia das ações lógicas do programa.

[2] Comentário enviado por ronyjah em 14/12/2015 - 16:15h


[1] Comentário enviado por mvforce em 14/12/2015 - 15:50h

Parabéns pelo tutorial... código bem claro e limpo.
Seria bastante didático se você colocasse antes do código fonte uma sequencia das ações lógicas do programa.


Este é meu primeiro artigo deve haver bastante falhas. Vou seguir seu conselho nos próximos (será tutorial sobre sistemas distribuídos, assim que sair gostaria de receber sua opinião). Muito obrigado prezado Mvforce.

[3] Comentário enviado por JairPMJr em 03/03/2016 - 08:21h

Para o seu primeiro artigo, esta muito bom.

Meus parabéns, o código bem comentado.

Abraço.

[4] Comentário enviado por camus88 em 21/04/2016 - 17:58h

Com ficaria com a função fork()? Estou estudando tal função mas ainda não consegui aplicá-la nesta situação. O fork(), diferente do Threads, ele duplica o processo criandoum semelhante filho alterando claro o PID(proces ID), minha duvida é onde colocaria está função(Linsten, Bind, Accept, Socket) para que assim tivesse multiplo acesso? Segue meu código de transferência de arquivo...

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#define NOME_ARQ_MAXLEN 128
#define TAMMAX_BLOCO 65536

int main(int argc, char *argv[]){
if (argc != 2) {
printf("uso: %s <porta>\n", argv[0]);
return 0;
}

int ls;
struct sockaddr_in addr;
struct sockaddr_in clt_addr;
int len_addr, clt;
char nome_arq[NOME_ARQ_MAXLEN];
int nr, ns;
int fd;
int nr_r;
char bloco[TAMMAX_BLOCO];

//socket()
ls = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ls == -1) {
perror("socket()");
return -1;
}

//bind()
addr.sin_port = htons(atoi(argv[1]));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(ls,
(struct sockaddr *)&addr,
sizeof(struct sockaddr_in)) == -1) {
perror("bind()");
return -1;
}

//listen()
if (listen(ls, 1024) == -1){
perror("listen()");
return -1;
}
while (1) {
len_addr = sizeof(struct sockaddr_in);
clt = accept(ls,
(struct sockaddr*)&clt_addr,
(socklen_t *)&len_addr);
if (clt == -1) {
perror("accept()");
continue;
}
bzero(nome_arq, NOME_ARQ_MAXLEN);
nr = recv(clt, nome_arq, NOME_ARQ_MAXLEN, 0);
if (nr == -1){
perror("recv()");
continue;
}
fd = open(nome_arq, O_RDONLY);
if (fd == -1) {
char *resp;
resp = strerror(errno);
send(clt, resp, strlen(resp), 0);
continue;
}
ns = send(clt, nome_arq, nr, 0);
if (ns == -1) {
perror("send(nome_arq)");
continue;
}

do {
bzero(bloco, TAMMAX_BLOCO);
nr_r = read(fd, bloco, TAMMAX_BLOCO);
if (nr_r > 0){
ns = send(clt, bloco, nr_r, 0);
if (ns == -1) {
perror("send(bloco)");
continue;
}
}
}while(nr_r > 0);
close(fd);
close(clt);
}
close(ls);
return 0;
}

[5] Comentário enviado por arthuraureliops em 19/03/2022 - 17:16h

Olá Ronaldo, gostaria de tirar uma dúvida com você, poderia me enviar seu contato?


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts