многопоточный веб-сервер в C - PullRequest
0 голосов
/ 02 мая 2020

Я пытаюсь создать многопоточный веб-сервер с использованием API POSIX. Это то, что я получил до сих пор:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>

const char CONTENTDIR[]="./contentdir" ; // this is the directory where keep all the files for requests
pthread_mutex_t mutex;

void error(const char *msg)
{
    perror(msg);
    exit(1);
}



struct user_input
     {
         int sockfd;      
         int thread_NO; 
         int buffer_size;
         pthread_t *thread_pool;
         int newsockfd;
     }input;



void httpWorker(int *);// This function will handle request
char * fType(char *);
char * responseHeader(int, char *);// function that builds response header
void *thread_pool(void *); //This function is called in main() function to create thread pool and create scheduling thread
void *sched_thread(void *); //This function is called in main_thread() function to call worker threads by using for loop




int main(int argc, char *argv[])
{
    pthread_t main_thread;
    int sockfd, newsockfd, portno;

    struct user_input *input = (struct user_input *)malloc(sizeof(struct user_input));
    input->thread_NO = atoi(argv[2]);//gathering user input of thread number
    input->buffer_size = atoi(argv[3]);//same for buffer size


    socklen_t clilen;
    struct sockaddr_in serv_addr, cli_addr;

    if (argc < 2) {
        fprintf(stderr,"ERROR, no port provided\n");
        exit(1);
    }

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0) 
       error("ERROR opening socket");

    bzero((char *) &serv_addr, sizeof(serv_addr));

    portno = atoi(argv[1]);

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
            error("ERROR on binding");

    listen(sockfd, input->buffer_size);


    while(1)
    {
      input->sockfd = sockfd;
      pthread_create(&main_thread, NULL, (void *)thread_pool, (void *)input);
      pthread_join(main_thread, NULL);
    }


    close(sockfd);

    return 0; 
}



void *thread_pool(void *input) 
{
    int worker_id;//index for creating thread pool

    //Casting info from struct argument passed from main()
    struct user_input *input_args = (struct user_input *)input;
    input_args->sockfd = ((struct user_input *)input)->sockfd;
    int thread_NO = ((struct user_input *)input)->thread_NO;


    //Getting ready to create thread pool and scheduling thread
    pthread_t thread_pool[thread_NO];
    pthread_t main_thread2;
    input_args->thread_pool = &thread_pool[thread_NO];


     //thread pool creating
    for(worker_id = 0; worker_id < sizeof(thread_NO); worker_id++)
    {   
        input_args->thread_pool[worker_id] = worker_id;
    }


    //creating scheduling thread
    pthread_create(&main_thread2, NULL, sched_thread, (void *)input_args);
    pthread_join(main_thread2, NULL);
}



void *sched_thread(void *input)
{

    int newsockfd;

    //gathering info from struct passed from main_thread()
    struct user_input *input_args = (struct user_input *)input;
    int sockfd = ((struct user_input *)input)->sockfd;
    input_args->buffer_size = ((struct user_input *)input)->buffer_size;


    socklen_t clilen;
    char buffer[256];
    struct sockaddr_in serv_addr, cli_addr;


    pthread_t *worker_thread = input_args->thread_pool;

    while(1)
        {
            clilen = sizeof(cli_addr);
            newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);

            if (newsockfd < 0) 
                error("ERROR on accept");



            for(int i = 0; i < input_args->buffer_size; i++)
            {  
                pthread_create(&worker_thread[i], NULL, (void *)httpWorker, &newsockfd);   
            }
            for(int i = 0; i < input_args->buffer_size; i++)
            {  
                pthread_detach(worker_thread[i]);       
            }
        }
    pthread_exit(NULL);
}



void httpWorker(int *sockfd)//sockfd contains all the information
{
    int newsockfd = *sockfd;// create a local variable for sockfd 
    char buffer[256];// we will read the data in this buffer
    char *token;// local variable to split the request to get the filename 
    bzero(buffer,256);// intialize the buffer data to zero
    char fileName[50];
    char homedir[50];
    char * type;
    strcpy(homedir,CONTENTDIR);// directory where files are stored.
    char *respHeader; //response header
    // start reading the message from incoming conenction

    if (read(newsockfd,buffer,255) < 0) 
      error("ERROR reading from socket");
    //get the requested file part of the request
    token = strtok(buffer, " ");// split string into token seperated by " "
    token = strtok(NULL, " ");// in this go we read the file name that needs to be sent
    strcpy(fileName,token);

    // get the complete filename 
    if(strcmp(fileName,"/")==0) // if filename is not provided then we will send index.html
        strcpy(fileName,strcat(homedir,"/index.html"));
    else
        strcpy(fileName,strcat(homedir,fileName));    
    type = fType(fileName);// get file type
    //open file and ready to send 
    FILE *fp;
    int file_exist=1;
    fp=fopen(fileName, "r"); 
    if (fp==NULL) file_exist=0; 
    respHeader = responseHeader(file_exist,type);
    if ((send(newsockfd, respHeader,strlen(respHeader), 0) == -1) || (send(newsockfd,"\r\n", strlen("\r\n"), 0) == -1))
      perror("Failed to send bytes to client");   

    free(respHeader);// free the allocated memory (note: the memory is allocated in responseheader function)

    if (file_exist)
    {
      char filechar[1];
      while((filechar[0]=fgetc(fp))!=EOF)
      {    
        if(send(newsockfd,filechar,sizeof(char),0) == -1) perror("Failed to send bytes to client");       
      } 
    }
    else
  {
    if (send(newsockfd,"<html> <HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY>Not Found</BODY></html> \r\n", 100, 0) == -1)
      perror("Failed to send bytes to client");          
  }

    close(newsockfd);
}


// function below find the file type of the file requested
char * fType(char * fileName){
     char * type; 
      char * filetype = strrchr(fileName,'.');// This returns a pointer to the first occurrence of some character in the string 
      if((strcmp(filetype,".htm"))==0 || (strcmp(filetype,".html"))==0)
            type="text/html";
      else if((strcmp(filetype,".jpg"))==0)
            type="image/jpeg";
      else if(strcmp(filetype,".gif")==0)
            type="image/gif";
      else if(strcmp(filetype,".txt")==0)
            type="text/plain";
      else
            type="application/octet-stream";

return type;
}



//buildresponseheader
char * responseHeader(int filestatus, char * type){
   char statuscontent[256] = "HTTP/1.0";
   if(filestatus==1){
            strcat(statuscontent," 200 OK\r\n");
            strcat(statuscontent,"Content-Type: ");
            strcat(statuscontent,type);
            strcat(statuscontent,"\r\n");
        }
   else {
            strcat(statuscontent,"404 Not Found\r\n");
            //send a blank line to indicate the end of the header lines   
            strcat(statuscontent,"Content-Type: ");
            strcat(statuscontent,"NONE\r\n");
        } 
   char * returnheader =malloc(strlen(statuscontent)+1);
   strcpy(returnheader,statuscontent);
   return returnheader;
}

Я создал «пул потоков», создав массив pthread_t в функции thread_pool. И sched_thread должен передать дескриптор файла, принятый каждому рабочему потоку. bash возвращает длинный список сообщений об ошибках во время работы, говорит Failed to send bytes to client: Bad file descriptor. И Failed to send bytes to client: Socket operation on non-socket появляется в самых первых двух строках. Интересно, это потому, что я не использовал мьютекс для блокировки каждого рабочего потока, или есть какая-либо ошибка использования API-интерфейсов pthread? Исходная серверная программа до того, как я добавил pthread, работает нормально, так как я тестировал под Ubuntu WSL.

Есть предложения?

1 Ответ

1 голос
/ 02 мая 2020

Нельзя передать ссылку на переменную в стеке новому потоку, как в

pthread_create(&worker_thread[i], NULL, (void *)httpWorker, &newsockfd);  

newsockfd хранится в стеке другого потока, и когда ваш новый поток запускает этот кадр стека где newsockfd был расположен, может даже больше не существовать или содержит другой кадр стека, или место в памяти было повторно использовано другой переменной стека.

Данные, передаваемые в поток, обычно должны быть либо статикой c памяти, либо выделенной памятью, а не стековой памятью, если только вы не можете гарантировать, что кадр стека наверняка останется замороженным до тех пор, пока новый поток не прочитает значение ( и поэтому вам придется блокировать поток создателя, пока новый поток не сообщит ему, что он скопировал значение).

Вы делаете это правильно при передаче input в

pthread_create(&main_thread, NULL, (void *)thread_pool, (void *)input);

Здесь вы явно выделяете память для передачи в поток.

Кстати, приведение к void * не имеет смысла; void * просто означает «любой указатель» (не совсем, но вы можете притвориться, что он это делает), и, таким образом, вы можете передать любой указатель для void * аргумента, который не требует приведения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...