Simples servidor http com concorrência feito em C

Publicado por Robson Lopes (última atualização em 05/08/2011)

[ Hits: 13.768 ]

Download http.tar.gz




Um simples servidor http com concorrência(fork ou thread) com objetivo de aumentar conhecimento na area de redes.
Há um makefile que possa ajudar na compilação do código.

Para rodar utilize o seguinte parametros:
./http <porta> -[fork ou thread]

Abra qualquer browser e teste. http://ipservidor:porta
Abraços

  



Esconder código-fonte

/*
 * Autor: Robson Oliveira
 * Obs.: Livre para qualquer alteração
 */

#include <stdio.h>          /* printf */
#include <stdlib.h>         /* exit */
#include <string.h>         /* bzero */
#include <sys/socket.h>     /* struct sockaddr, socket, listen, bind, accept, recv, send */
#include <sys/wait.h>       /* waitpid */
#include <arpa/inet.h>      /* struct sockaddr */
#include <unistd.h>         /* exit, fork */
#include <signal.h>         /* signal */
#include <time.h>           /* TIME, time_t */
#include <pthread.h>        /* pthread_t, pthread_create */
#include <sys/stat.h>       /* lstat() */
#include <sys/types.h>      /* mode_t */

/*define variaveis constantes e seus respectivos valores */
#define BUFFSIZE 800                
#define MAXPENDING 5                
#define SA struct sockaddr
#define SAI struct sockaddr_in
#define TYPE 16               
#define tipoData "%a, %m %b %Y %X %Z" 
#define SERVER "AS(2008-2011)"      

void error(char *msg); /* imprime mensagens de erro */
void sig_chld(int sinal); /* trata o sinal, para evitar filhos zumbis */
char *extensao(char *nome); /* verifica e retorna a extensao do arquivo para respota HTTP*/
int redirect(char *caminho); /* Verifica se o arquivo não sofreu uma modificação permanente*/
void respostaHTTP(char *resposta, char *tipo, char* caminho, int connfd, char *size); /* Cria uma resposta Http e envia ao cliente*/
void enviaArquivo(char *caminho, int connfd); /* Envia o arquivo solicitado ao Cliente */
long verificaArquivo(char *caminho); /* Verifica se o arquivo solicitado existe na raiz */
/* Verifica o que é pedido no protocolo Http solicitado pelo cliente*/
int TratandoPedido(char *metodo, char *versao, char *caminho, char *p, char *tipo, int i);
int pedidoHTTP(char *p, char *caminho, char *tipo);/* Manipula pedido do cliente essa função necessita da TratandoPedido */
int *criarSocket(int porta); /* Cria um socket utilizando TCP/IP */
void dec_string(long size, char *s); /* Transforma um inteiro em uma String*/
void execucao(int connfd); /* execucao da conexao com o metodo fork */
void in_fork(int *listenfd); /* metodo que utiliza o fork para execucao */
static void *execucao_thread(void *arg); /* execucao da conexao com o metodo thread */
void in_thread(int *listenfd); /* metodo que utiliza threads para execucao */

/* Função Principal*/
int main(int argc, char *argv[]){
  
  int *listenfd, porta, concorrencia;
  
  /* Caso o usuario nao coloque os 3 parametros necessarios */
  if(argc != 3)
    error("Use: HttpServer <porta> -[thread ou fork]");
  
  /* define o modelo de concorrencia */
  if(strcmp(argv[2], "-thread") == 0)
    concorrencia = 1;
  else if(strcmp(argv[2], "-fork") == 0)
    concorrencia = 2;
  else
    error("Por favor, escolha a concorrência sendo ela -fork ou -thread .");
  
  porta = atoi(argv[1]); /*define a porta */
  listenfd = criarSocket(porta); /* chama a funcao criarSocket */
  
  /* Estipula a fila para o Servidor */
  if(listen(*listenfd, MAXPENDING) < 0)
    error("Falha ao tentar escutar o socket do servidor");
  
  if(concorrencia == 2) /* se concorrencia for fork */
    signal(SIGCHLD, sig_chld); /* vai tratar os filhos zumbis */
  
  
  if(concorrencia == 1){ /* se concorencia for thread */
    in_thread(listenfd); /* chama o metodo in_thread */
  }
  else if(concorrencia == 2){ /* se concorrencia for fork */
    in_fork(listenfd); /* chama o metodo in_fork */
  }
  
  free(listenfd);
  return 0;
}

/* Imprime mensagens de erro */
void error(char *msg){
  printf("%s\n", msg);
  exit(0);
  return;
}

/* verifica e retorna a extensao do arquivo */
char *extensao(char *nome){
  char ext[20];
  strcpy(ext, ".");
  strcat(ext, nome);
  
  if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) return "text/html";
  if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg";
  if (strcmp(ext, ".gif") == 0) return "image/gif";
  if (strcmp(ext, ".png") == 0) return "image/png";
  if (strcmp(ext, ".css") == 0) return "text/css";
  if (strcmp(ext, ".au") == 0) return "audio/basic";
  if (strcmp(ext, ".wav") == 0) return "audio/wav";
  if (strcmp(ext, ".avi") == 0) return "video/x-msvideo";
  if (strcmp(ext, ".mpeg") == 0 || strcmp(ext, ".mpg") == 0) return "video/mpeg";
  if (strcmp(ext, ".mp3") == 0) return "audio/mpeg";
  if (strcmp(ext, ".js") == 0) return "text/javascript";
  if (strcmp(ext, ".ico") == 0) return "image/x-icon";
  
  return NULL;
}

void respostaHTTP(char *resposta, char *tipo, char *caminho, int connfd, char *size){
  time_t rawtime;
  struct tm *timeinfo, *ltime;
  struct stat arq;
  char timebuf[50], encaminhar[BUFFSIZE], *aux, lastime[50];
  long s;
  
  lstat(caminho, &arq);
  
  time(&rawtime);
  timeinfo = localtime(&rawtime);
  ltime = localtime(&arq.st_mtime);
  
  strftime(lastime, sizeof(lastime), tipoData, ltime);
  strftime(timebuf, sizeof(timebuf), tipoData, timeinfo);  
  
  if(strcmp(resposta, "HTTP/1.1 200 OK") == 0){
    strcpy(encaminhar, resposta);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Connection: close\r\n");
    strcat(encaminhar, "Date: ");
    strcat(encaminhar, timebuf);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Server: ");
    strcat(encaminhar, SERVER);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Content-Length: ");
    strcat(encaminhar, size);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Last-Modified: ");
    strcat(encaminhar, lastime);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Content-Type: ");
    if(strcmp(tipo, "/") == 0){
      aux = extensao("html");
      strcat(encaminhar, aux);
    }
    else{
      aux = extensao(tipo);
      if(aux != NULL)
   strcat(encaminhar, aux);
      else{
   s= verificaArquivo("badrequest.html");
   dec_string(s, size);
   respostaHTTP("HTTP/1.1 400 Bad Request", tipo, "badrequest.html", connfd, size);
   enviaArquivo("badrequest.html", connfd);
   close(connfd);
   exit(0);
      }
    }
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "\r\n");
    printf("%s\n", encaminhar);
    send(connfd, encaminhar, strlen(encaminhar), 0);
  }
  else if(strcmp(resposta, "HTTP/1.1 404 Not Found") == 0){
    strcpy(encaminhar, resposta);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Connection: close\r\n");
    strcat(encaminhar, "Date: ");
    strcat(encaminhar, timebuf);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Server: ");
    strcat(encaminhar, SERVER);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Content-Length: ");
    strcat(encaminhar, size);
    strcat(encaminhar, "\r\n");   
    strcat(encaminhar, "Last-Modified: ");
    strcat(encaminhar, lastime);
    strcat(encaminhar, "\r\n");    
    strcat(encaminhar, "Content-Type: ");   
    aux = extensao("html");
    strcat(encaminhar, aux);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "\r\n");
    printf("%s\n", encaminhar);
  }
  else if(strcmp(resposta, "HTTP/1.1 505 HTTP Version Not Supported") == 0){
    strcpy(encaminhar, resposta);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Connection: close\r\n");
    strcat(encaminhar, "Date: ");
    strcat(encaminhar, timebuf);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Server: ");
    strcat(encaminhar, SERVER);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Content-Length: ");
    strcat(encaminhar, size);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Last-Modified: ");
    strcat(encaminhar, lastime);
    strcat(encaminhar, "\r\n");    
    strcat(encaminhar, "Content-Type: ");   
    aux = extensao("html");
    strcat(encaminhar, aux);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "\r\n");
    printf("%s\n", encaminhar);
  }
  else if(strcmp(resposta, "HTTP/1.1 400 Bad Request") == 0){
    strcpy(encaminhar, resposta);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Connection: close\r\n");
    strcat(encaminhar, "Date: ");
    strcat(encaminhar, timebuf);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Server: ");
    strcat(encaminhar, SERVER);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Content-Length: ");
    strcat(encaminhar, size);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Last-Modified: ");
    strcat(encaminhar, lastime);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Content-Type: ");   
    aux = extensao("html");
    strcat(encaminhar, aux);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "\r\n");
    printf("%s\n", encaminhar);
  }
  else if(strcmp(resposta, "HTTP/1.1 301 Moved Permanently") == 0){
    strcpy(encaminhar, resposta);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Connection: close\r\n");
    strcat(encaminhar, "Date: ");
    strcat(encaminhar, timebuf);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Server: ");
    strcat(encaminhar, SERVER);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Content-Length: ");
    strcat(encaminhar, size);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Location: ");
    strcat(encaminhar, caminho);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Last-Modified: ");
    strcat(encaminhar, lastime);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "Content-Type: ");   
    aux = extensao("html");
    strcat(encaminhar, aux);
    strcat(encaminhar, "\r\n");
    strcat(encaminhar, "\r\n");
    printf("%s\n", encaminhar);
  }
  
  return;
}

/* Envia o arquivo solicitado ao cliente */
void enviaArquivo(char *caminho, int connfd){
  FILE *file;
  long size;
  char *c;
  
  file = fopen(caminho, "rb");
  
  if(file){
    fseek(file , 0 , SEEK_END);
    size = ftell(file);
    rewind(file);
    c = (char*) malloc(sizeof(char) * size);
    fread(c, 1, size, file);
    send(connfd, c, size, 0);
    fclose(file);
    free(c);
  }
  return;
}

long verificaArquivo(char *caminho){
  FILE *file;
  long size;
  file = fopen(caminho, "rb");
  if(file){
    fseek(file , 0 , SEEK_END);
    size = ftell(file);
    rewind(file);
    fclose(file);
    return size;
  }
  else
    return 0;
}

int TratandoPedido(char *metodo, char *versao, char *caminho, char *p, char *tipo, int i){
  char *HTTP="HTTP/1.1";
  int k;
  
  for(i = i+1, k = 0; p[i] != ' ' && p[i] != '{FONTE}'; i++, k++)
      caminho[k] = p[i];
    caminho[k] = '{FONTE}';
    
    for(i = i+1, k = 0; p[i] != '{FONTE}' && k < 8; i++, k++)
      versao[k] = p[i];
    versao[k] = '{FONTE}';
    
    if((strcmp(HTTP, versao) != 0) && (strcmp("HTTP/1.0", versao) != 0))
      return 0;
    
    if(strcmp(caminho, "/") != 0){
      for(i = strlen(caminho); p[i] != '.' && i >=0; i--);
      for(i = i+1, k = 0; p[i] != '{FONTE}' && p[i] != ' '; i++, k++)
   tipo[k] = p[i];
      tipo[k] = '{FONTE}';
    }
    else
      strcpy(tipo, "/");
      
  return 1;
}

int pedidoHTTP(char *p, char *caminho, char *tipo){
  char *GET="GET", *POST="POST", metodo[9], versao[10];
  char aux;
  
  int i, k, l;
  
  for(i = 0; p[i] != ' ' && p[i] != '{FONTE}' && i < 9; i++)
    metodo[i] = p[i];
  metodo[i] = '{FONTE}';
  
  if(strcmp(GET, metodo) == 0){
    if(!TratandoPedido(metodo, versao, caminho, p, tipo, i))
      return 2;
  }
  else if(strcmp(POST, metodo) == 0){
    i = strlen(p) - 1;
    for(k = 0; p[i] != '\n'; k++, i--)
      tipo[k] = p[i];
    tipo[k] = '{FONTE}';

    l = strlen(tipo) - 1;
    for(k = 0, i = l; k <= l/2; k++, i--){
      aux = tipo[k];
      tipo[k] = tipo[i];
      tipo[i] = aux;
    }
    return 3;
  }
  else{
    return 0;
  }
    
  return 1;
}

/* Cria um socket TCP/IP */
int *criarSocket(int porta){
  int *listenfd;
  struct sockaddr_in servaddr; /* Define um socket para o servidor */
  
  listenfd = (int *) malloc(sizeof(int)); /* malloc define um ponteiro para um espaco de memoria do tamanho solicidado */
  
  /* chama a funcao socket para especificar o tipo do protocolo */
  if((*listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    error("Falha ao criar o socket");
  
  /*populando os dados do servidor*/
  bzero(&servaddr, sizeof(servaddr)); /*zera a estrutura que armazenarah os dados do servidor */
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Aceita qualquer faixa de IP que a maquina possa responder. */
  servaddr.sin_port = htons(porta); /* define a porta */

  /* vincula um socket a um endereco */
  if(bind(*listenfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
    error("Falha ao observar o socket do servidor");
  
  return listenfd;
}

int redirect(char *caminho){
  FILE *file;
  char novo[BUFFSIZE], antigo[BUFFSIZE];
  file = fopen("htaccess.serv", "rb");
  
  if(file){
    while(!feof(file)){
      fscanf(file, "%s %s", antigo, novo);
      if(strcmp(antigo, caminho) == 0){
   strcpy(caminho, novo);
   return 1;
      }
    }
    return 0;
  }
  else
    return 0;
}

void dec_string(long size, char *s){
  int i, j, l;
  char aux;
  
  for(i = 0; size > 0; size = size/10, i++){
    s[i] = '0' + ((size%10) - 0);
  }
  s[i] = '{FONTE}';
  
  j = strlen(s) - 1;
  for(l = 0; l < i/2; l++, j--){
    aux = s[l];
    s[l] = s[j];
    s[j] = aux;
  }
  
  return;
}

void execucao(int connfd){
  char buffer[BUFFSIZE], caminho[BUFFSIZE], tipo[BUFFSIZE], sizechar[TYPE];
  char POST[BUFFSIZE];
  int situacao, n, i, novo;
  long size;
  
  /*Rebendo Protocolo http do cliente*/
  if((n = recv(connfd, buffer, BUFFSIZE, 0)) < 0)
    error("Falhou ao receber os dados iniciais do cliente");
  buffer[n] = '{FONTE}';
  
  printf("%s\n", buffer);
  printf("-------------------- Resposta Servidor -------------------\n");
  situacao = pedidoHTTP(buffer, caminho, tipo);
       
  if(situacao == 1){   
    /*Verica se o link foi mudado, veja o arquivo htaccess.serv.*/
    novo = redirect(caminho);
      
    if(strcmp(caminho, "/") == 0)
      strcpy(caminho, "index.html");
    else{
      for(i = strlen(caminho); i>=0; i--)
   caminho[i+1] = caminho[i];
      caminho[0] = '.';
    }
      
    size = verificaArquivo(caminho);
    if(size){
      dec_string(size, sizechar);
      if(!novo){
   respostaHTTP("HTTP/1.1 200 OK", tipo, caminho, connfd, sizechar);
   enviaArquivo(caminho, connfd);
      }
      else{
   respostaHTTP("HTTP/1.1 301 Moved Permanently", tipo, caminho, connfd, sizechar);
   enviaArquivo(caminho, connfd);
      }
    }
    else{
      size = verificaArquivo("notfound.html");
      dec_string(size, sizechar);
      respostaHTTP("HTTP/1.1 404 Not Found", "html", caminho, connfd, sizechar);
      enviaArquivo("notfound.html", connfd);
    }
  }
  else if(situacao == 2){
    size = verificaArquivo("notsupported.html");
    dec_string(size, sizechar);
    respostaHTTP("HTTP/1.1 505 HTTP Version Not Supported", "html", caminho, connfd, sizechar);
    enviaArquivo("notsupported.html", connfd);
  }
  else if(situacao == 3){
    size = sizeof(tipo);
    dec_string(size, sizechar);
    respostaHTTP("HTTP/1.1 200 OK", "html", caminho, connfd, sizechar);
    strcpy(POST, "<hmtl>\n<head>\n<title>Post</title>\n</head>\n<body>");
    strcat(POST, "\n<b>Post:</b> ");
    strcat(POST, tipo);
    strcat(POST, "\n</body>\n</html>");
    send(connfd, POST, sizeof(POST), 0);
  }
  else{
    size = verificaArquivo("badrequest.html");
    dec_string(size, sizechar);      
    respostaHTTP("HTTP/1.1 400 Bad Request", "html", caminho, connfd, sizechar);
    enviaArquivo("badrequest.html", connfd);
  }
  printf("--------------------- Fim Comunicação --------------------\n\n");
    
  return;
}

/* Trata o sinal enviado pelo sistema */
void sig_chld(int sinal){
  pid_t pid;
  int stat;
  while((pid = waitpid(-1, &stat, WNOHANG)) > 0);
  return;
}

void in_fork(int *listenfd){
  struct sockaddr_in client;
  int connfd;
  socklen_t clientlen;
  pid_t pid;
  
  for( ; ; ){
    clientlen = sizeof(client);
    /* aceita a conexao com o cliente */
    if((connfd = accept(*listenfd, (SA *) &client, &clientlen)) < 0)
      error("Falhou ao aceitar a conexao do cliente");
    
    printf("------------------ Pedido de: %s ------------------\n", inet_ntoa(client.sin_addr)); /* imprime IP do cliente */
    
    if((pid = fork()) == 0){ 
      close(*listenfd);
      execucao(connfd);
      close(connfd); /* fecha a conexao */
      exit(0);
    }
    close(connfd); /*fecha a conexao*/
  }
  close(*listenfd); /*fecha a escuta*/
  exit(0);
  return;
}

/* Função de execução da Thread */
static void *execucao_thread(void *arg){
  int connfd;
  
  connfd = *((int *) arg);
  pthread_detach(pthread_self());
  execucao(connfd);
  close(connfd);
  
  return NULL;
}

/* metodo que utiliza threads para execucao */
void in_thread(int *listenfd){
  struct sockaddr_in client; /* define um socket para o cliente */
  socklen_t clientlen;
  int *iptr;
  pthread_t tid;
  
  for( ; ; ){
    iptr = (int *) malloc(sizeof(int)); /* aloca iptr para cada thread */
    *iptr = accept(*listenfd, (SA *) &client, &clientlen); /* iptr aceita a escuta do cliente */
    printf("------------------ Pedido de: %s ------------------\n", inet_ntoa(client.sin_addr)); /* imprime o endereco IP do cliente */
    /* cria uma thread */
    pthread_create(&tid, NULL, &execucao_thread, iptr);
  }
  return;
}

Scripts recomendados

Simulação de controle de fluxo usando sockets

Servidor em C via UDP

Servidor TCP/IP em C

Programa em C para monitorar IPs e portas ativas

Sockets: um mini webserver


  

Comentários
[1] Comentário enviado por Tacioandrade em 06/08/2011 - 15:11h

Meus parabéns cara, muito bom o código. =)

Não vou me aprofundar muito nele, pois não sou tão bom em C assim, mais depois darei uma boa olhada nele.



Abraço.


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts