C语言 多线程Web服务器

ehxuflar  于 2022-12-29  发布在  其他
关注(0)|答案(1)|浏览(150)

我正在尝试使用POSIX API构建一个多线程Web服务器。这是我目前的成果:

#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;
}

我通过在thread_pool函数中创建一个pthread_t数组来构建一个"线程池"。sched_thread将把接受的文件描述符传递给每个工作线程。bash在运行时返回一长串错误消息,Failed to send bytes to client: Bad file descriptor出现在最前面的两行。我想知道这是不是因为我还没有t使用互斥锁来锁定每个工作线程,或者使用pthread API有什么错误吗?在我添加pthread之前,原始的服务器程序在ubuntu wSL下测试时工作正常。
有什么建议吗?

3lxsmp7m

3lxsmp7m1#

不能将对堆栈上变量的引用传递给新线程,如

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

newsockfd存储在不同线程的堆栈上,当新线程开始运行时,newsockfd所在的堆栈帧可能不再存在,或者包含不同的堆栈帧,或者内存位置已被不同的堆栈变量重用。
传递给线程的数据通常必须是静态内存或已分配内存,决不能是堆栈内存,除非你能保证堆栈帧在新线程读取该值之前一定会保持冻结状态(因此你必须阻塞创建者线程,直到新线程让它知道它已经复制了该值)。
传入input时,您的操作正确

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

这里显式地分配要传递给线程的内存。
顺便说一下,强制转换为void *是没有意义的; void *只是表示“任意指针”(不是真的,但您可以假装它是),因此您可以为void *参数传入任意指针,这不需要强制转换。

相关问题