Simples servidor http com concorrência feito em C
Publicado por Robson Lopes (última atualização em 05/08/2011)
[ Hits: 13.816 ]
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; }
Exemplo de sockets: um client e um server bem simples
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
Compartilhamento de Rede com samba em modo Público/Anônimo de forma simples, rápido e fácil
Cups: Mapear/listar todas as impressoras de outro Servidor CUPS de forma rápida e fácil
Criando uma VPC na AWS via CLI
Tem como instalar o gerenciador AMD Adrenalin no Ubuntu 24.04? (9)