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: 13.522 ]

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

Controlando UPLOAD com o CBQ

Monitorando o consumo de banda com Bwbar

PostgreSQL - Embutindo comandos SQL no seu código C

Ensaio acerca de bibliotecas de código aberto para abstração de acesso a banco de dados em linguagem 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;
}


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner
Linux banner
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts