我正在尝试使用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下测试时工作正常。
有什么建议吗?
1条答案
按热度按时间3lxsmp7m1#
不能将对堆栈上变量的引用传递给新线程,如
newsockfd
存储在不同线程的堆栈上,当新线程开始运行时,newsockfd
所在的堆栈帧可能不再存在,或者包含不同的堆栈帧,或者内存位置已被不同的堆栈变量重用。传递给线程的数据通常必须是静态内存或已分配内存,决不能是堆栈内存,除非你能保证堆栈帧在新线程读取该值之前一定会保持冻结状态(因此你必须阻塞创建者线程,直到新线程让它知道它已经复制了该值)。
传入
input
时,您的操作正确这里显式地分配要传递给线程的内存。
顺便说一下,强制转换为
void *
是没有意义的;void *
只是表示“任意指针”(不是真的,但您可以假装它是),因此您可以为void *
参数传入任意指针,这不需要强制转换。