Reputation: 1
I'm trying to build a multithreaded web server using POSIX APIs. This is what I've got so far:
#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;
}
I built a "thread pool" by creating an array of pthread_t in thread_pool function. And the sched_thread is to pass file descriptor accepted to each of the worker thread. The bash returns a long list of error messages while running, says Failed to send bytes to client: Bad file descriptor
. And Failed to send bytes to client: Socket operation on non-socket
appears in the very first two lines.
I wonder if it's because I haven't used mutex to lock each of the worker thread or is there any error of using pthread APIs? The original server program before I added pthread works fine as I tested under ubuntu WSL.
Any suggestions?
Upvotes: 0
Views: 1677
Reputation: 133189
You cannot pass a reference to a variable on stack to a new thread as in
pthread_create(&worker_thread[i], NULL, (void *)httpWorker, &newsockfd);
newsockfd
is stored on the stack of a different thread and when your new thread starts running that stack frame where newsockfd
was located may not even exist any longer or contains a different stack frame or the memory location has been reused by a different stack variable.
Data passed to a thread must usually either be static memory or allocated memory, never stack memory, unless you can guarantee that the stack frame will for sure stay frozen until the new thread has read the value (and therefor you'd have to block the creator thread until the new thread lets it know that is has copied the value).
You are doing it correctly when passing input
in
pthread_create(&main_thread, NULL, (void *)thread_pool, (void *)input);
Here you explicitly allocate memory to pass to the thread.
By the way, the cast to void *
is meaningless; void *
just means "any pointer" (not really but you can pretend it does) and thus you can pass in any pointer for void *
argument, that requires no casting.
Upvotes: 1