Simples servidor http com concorrência feito em C
Publicado por Robson Lopes (última atualização em 05/08/2011)
[ Hits: 13.789 ]
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
/* * 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; }
Enviar mensagem ao usuário trabalhando com as opções do php.ini
Meu Fork do Plugin de Integração do CVS para o KDevelop
Compartilhando a tela do Computador no Celular via Deskreen
Como Configurar um Túnel SSH Reverso para Acessar Sua Máquina Local a Partir de uma Máquina Remota
Configuração para desligamento automatizado de Computadores em um Ambiente Comercial
Criando uma VPC na AWS via CLI
Multifuncional HP imprime mas não digitaliza
Dica básica para escrever um Artigo.
Como Exibir Imagens Aleatórias no Neofetch para Personalizar seu Terminal
Acabei zuando meu Linux inteiro e estou desesperado (2)