////////////////////////////////////////////////////////////////////////////// // // File Name: server.cxx // // Overview: A simple web server to retrieve and return files based in the // /www/docs directory. // // Approach : Use the standard socket setup provided by the instructor. Once // the socket is setup, start waiting for HTTP requests. When // one is received, process it based on the mode. // // Notes: By J. Kyle Glowacki // ////////////////////////////////////////////////////////////////////////////// #define closesocket close #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::string; using std::endl; enum MODE_T {DEFAULT, FORK, THREAD}; const int PROTOPORT = 1234; // default protocol port number const int QLEN = 6; // size of request queue // global variables MODE_T mode = DEFAULT; string BASE = "/home/fridge/glowacj"; //string BASE = "/www/docs"; const int threadCount = 50; // number of threads created pthread_t *threadIds = 0; bool threadComplete[threadCount]; // flag for the thread to tell the receiver // that is has completed. bool threadInUse[threadCount]; // flag to indicate that a thread is in use. /////////////////////////////////////////////////////////////////////////////// // // Method : copyTo // // Inputs : The starting index into a character array, an input character // array, an output character array, and the character to stop the // copy on. // // Outputs : A buffer containing the contents of the from buffer starting // at character position 'start' and ending at the first position // after start which contained the 'till' character. // // Approach : Loop through the characters until the 'till' character is found. // Add a null to the end of the buffer // // Notes : No checking of the sizes of the input and output arrays will // be performed. The user should ensure that the input and output // buffers have sufficient space. // /////////////////////////////////////////////////////////////////////////////// void copyTo(int start, char *from, char *to, char till) { unsigned int index = 0; while (from[start] != till) to[index++] = from[start++]; to[index] = '\0'; } /////////////////////////////////////////////////////////////////////////////// // // Method : contains // // Inputs : A null terminated buffer to look in and a null terminated string // to look for. // // Outputs : The index into the buffer which contains the pattern or -1, if // the pattern was not found. // // Approach : Walk through the buffer keeping track of whether we found it or // not. // // Notes : NONE // /////////////////////////////////////////////////////////////////////////////// int contains(char *buffer, char *pattern) { unsigned int index = 0; unsigned int bufferLength(strlen(buffer)); unsigned int charsToMatch(strlen(pattern)); unsigned int matched(0); // match string is bigger than buffer. if (charsToMatch > bufferLength) return -1; // loop through and match all for (index = 0;index < bufferLength;++index) { if (buffer[index] == pattern[matched]) { ++matched; if (matched == charsToMatch) { // had a complete match return index; } } else { matched = 0; } } return -1; } /////////////////////////////////////////////////////////////////////////////// // // Method : fixPath // // Inputs : The requested file name. // // Outputs : The full path to the specified file. // // Approach : If the filename does not begin with a /, then add one. Prepend // the document base to the filename. If the file name ends in a // slash, append index.html to the end. // // Notes : NONE // /////////////////////////////////////////////////////////////////////////////// void fixPath(string& in) { if (in[0] != '/') in = "/" + in; // prepend the file path in = BASE + in; // check for trailing '/' // if so add index.html if (in[in.size()-1] == '/') { in = in + "index.html"; } } /////////////////////////////////////////////////////////////////////////////// // // Method : doItAsDefault // // Inputs : The socket to work with // // Outputs : NONE // // Approach : Receive the request, find the requested filename in the request, // extract it, if the file exists, send it, otherwise send an // error message. // // Notes : NONE // /////////////////////////////////////////////////////////////////////////////// void doItAsDefault(int sd2) { char ubuf[5000]; char buf[1000]; if (recv(sd2, ubuf, sizeof(ubuf), 0) > 0) { int location(0); if ((location = contains(ubuf, "GET")) < 0) { // does not contain a get request. } else { // does contain a get. copyTo(location+2, ubuf, buf, ' '); string temp = buf; fixPath(temp); // convert string to char array for system call char *t = (char *)malloc(temp.size()+1 * sizeof(char)); for (unsigned short i = 0;i < temp.size();++i) t[i] = temp[i]; t[temp.size()] = 0; // try to open file FILE * fileToSend = NULL; if ((fileToSend = fopen(t, "r")) != 0) { // notify the requestor, that we found the file sprintf(buf, "HTTP/1.0 200 OK\r\n\r\n"); send(sd2,buf,strlen(buf),0); unsigned int size = 1; // send all the contents. while (size > 0) { memset((char *)&buf,0,1000); size = fread(buf, 1, 1000, fileToSend); if (size > 0) send(sd2,buf,size,0); } } else { // file could not be opened sprintf(buf, "HTTP/1.0 404 Not Found\r\n\r\n"); send(sd2,buf,strlen(buf),0); sprintf(buf, "404 Not Found\r\n\r\n"); send(sd2,buf,strlen(buf),0); } fclose(fileToSend); free(t); } } closesocket(sd2); } /////////////////////////////////////////////////////////////////////////////// // // Method : doItWithFork // // Inputs : The socket to work with. // // Outputs : NONE // // Approach : Fork a child, then close the socket. The child calls the // default processing and exits. // // Notes : NONE // /////////////////////////////////////////////////////////////////////////////// void doItWithFork(int sd2) { pid_t pid = 0; if ((pid = fork()) < 0) { // child fork failure. cerr << "Failure to spawn process." << endl; // handle the request ourselves. doItAsDefault(sd2); return; } else { if (pid == 0) { // we are now the child doItAsDefault(sd2); // done with that request, so exit exit(1); } else { // we are now the parent closesocket(sd2); return; } } } /////////////////////////////////////////////////////////////////////////////// // // Function : ThreadMethod // // Inputs : A void pointer to an argument list. The arguments are integers. // The first arguments is the thread number of this thread, the // second argument is the socket number this thread should be // working with. // // Ouputs : NONE // // Approach : Recover the arguments, then call the default processing method. // Once that finished, mark the shared buffer to indicate that // we have finished and can be re-joined. // // Notes : This is the main method that the threads will execute. // /////////////////////////////////////////////////////////////////////////////// void *ThreadMethod(void *arg) { int *recover = (int *)arg; int threadNum = recover[0]; int sd2 = recover[1]; doItAsDefault(sd2); threadComplete[threadNum] = true; free(recover); return (void *)0; } /////////////////////////////////////////////////////////////////////////////// // // Method : getOpenThreadNumber // // Inputs : NONE // // Outputs : The thread number to be assigned to the new thread. This number // is used to index into 2 parallel arrays which indicate if a // thread is in use, or if the thread is finished and can be // re-joined. // // Approach : Try to avoid walking the list by checking the nextGuess index // first. // // Notes : NONE // /////////////////////////////////////////////////////////////////////////////// int getOpenThreadNumber() { static unsigned short int nextGuess = 0; // try to access it quickly if (!threadInUse[nextGuess]) { threadInUse[nextGuess] = true; // return the current value and increment it. return nextGuess++; } // the guess was wrong, linear search for open one. for (unsigned short int i = 0;i < threadCount;++i) { if (!threadInUse[i]) { threadInUse[i] = true; return i; } } // no openings found return -1; } /////////////////////////////////////////////////////////////////////////////// // // Method : doItWithThreads // // Inputs : The socket to work with. // // Outputs : NONE // // Approach : Check to see if any more threads are available, if not, then // handle the request ourselves. If there are threads, then make // a new one to handle the request. // // Notes : NONE // /////////////////////////////////////////////////////////////////////////////// void doItWithThreads(int sd2) { int *temp = (int *)malloc(2 * sizeof(int)); int threadNum = getOpenThreadNumber(); if (threadNum < 0) { // no threads available to do request, so... // handle the request ourselves. doItAsDefault(sd2); return; } // otherwise, we have the thread id, so now is the time to create the thread // and get back to the main loop. temp[0] = threadNum; temp[1] = sd2; int returnValue; if (returnValue = pthread_create(&(threadIds[threadNum]), NULL, ThreadMethod, (void *)temp) != 0) { cerr << "Error creating thread " << threadNum << endl; // count the number of threads in use, to assist in determinations // as to why the create call failed. unsigned short int threadsInUse = 0; for (unsigned short int i = 0;i < threadCount;++i) if (threadInUse[i]) ++threadsInUse; cerr << " There are currently " << threadsInUse << " threads in use." << endl << endl; // Print out the man page test associated with each error code. switch (returnValue) { case ENOMEM: cerr << " ENOMEM The system lacked the necessary " << "resources to" << endl; cerr << " create another thread." << endl; case EINVAL: cerr << " EINVAL The value specified by attr is invalid." << endl; case EPERM: cerr << " EPERM The caller does not have appropriate " << "permission to" << endl; cerr << " set the required scheduling parameters " << "or scheduling policy." << endl; } cerr << "Error Code = " << returnValue << endl; // Handle the request ourselves. doItAsDefault(sd2); } } /////////////////////////////////////////////////////////////////////////////// // // Method : checkMail // // Inputs : NONE // // Outputs : NONE // // Approach : Loop through all the completion flags and if the thread is // complete then re-join the thread. // // Notes : NONE // /////////////////////////////////////////////////////////////////////////////// void checkMail() { // loop through all the possible thread numbers for (unsigned short int i = 0;i < threadCount;++i) // if this thread is complete if (threadComplete[i]) { // then rejoin it if (pthread_join(threadIds[i], NULL) != 0) { cerr << "Error rejoining thread " << i << endl; } // and mark it as no complete and not in use. threadComplete[i] = false; threadInUse[i] = false; } } /////////////////////////////////////////////////////////////////////////////// // // Method : main // // Inputs : Two optional inputs. // 1st = port number to operate on. // 2nd = mode to operate in (fork or thread). // // Outputs : Exit code. // // Approach : Use the standard socket connection stuff provided and then setup // anything needed for our particular mode before entering the main // processing loop. // // Notes : NONE // /////////////////////////////////////////////////////////////////////////////// int main(int argc, char *argv[]) { struct protoent *ptrp; /* pointer to a protocol table entry */ struct sockaddr_in sad; /* structure to hold server's address */ struct sockaddr_in cad; /* structure to hold client's address */ int sd, sd2; /* socket descriptors */ int port; /* protocol port number */ int alen; /* length of address */ memset((char *)&sad,0,sizeof(sad)); /* clear sockaddr structure */ sad.sin_family = AF_INET; /* set family to Internet */ sad.sin_addr.s_addr = INADDR_ANY; /* set the local IP address */ /* Check command-line argument for protocol port and extract */ /* port number if one is specified. Otherwise, use the default */ /* port value given by constant PROTOPORT */ if (argc == 1) port = PROTOPORT; if (argc == 2) port = atoi(argv[1]); if (argc == 3) { port = atoi(argv[1]); if (strcmp(argv[2], "fork") == 0) { cout << "Fork mode." << endl; mode = FORK; } if (strcmp(argv[2], "thread") == 0) { cout << "Thread mode." << endl; mode = THREAD; } } cout << "Operating on port : " << port << endl; if (port > 0) { /* test for illegal value */ sad.sin_port = htons((u_short)port); } else { /* print error message and exit */ cerr << "Bad port number : " << argv[1] << endl; exit(1); } /* Map TCP transport protocol name to protocol number */ if (((int)(ptrp = getprotobyname("tcp"))) == 0) { cerr << "Can not map 'tcp' to protocol number" << endl; exit(1); } /* Create a socket */ sd = socket(PF_INET, SOCK_STREAM, ptrp->p_proto); if (sd < 0) { cerr << "Socket Creation Failed" << endl; exit(1); } /* Bind a local address to the socket */ if (bind(sd, (struct sockaddr *)&sad, sizeof(sad)) < 0) { cerr << "Bind Failed" << endl; exit(1); } /* Specify size of request queue */ if (listen(sd, QLEN) < 0) { cerr << "Listen Failed" << endl; exit(1); } // if Thread, then there is some setup required. if (mode == THREAD) { // need to dynamically allocate this. // pthreads does not accept it, if it is a fixed array. threadIds = (pthread_t *)malloc(threadCount * sizeof(pthread_t)); if (threadIds == 0) { cerr << "Error allocating memory." << endl; return -1; } for (unsigned short int i = 0;i < threadCount;++i) { threadComplete[i] = false; threadInUse[i] = false; } } // Main server loop - accept and handle requests while (1) { alen = sizeof(cad); if ((sd2=accept(sd, (struct sockaddr *)&cad, &alen)) < 0) { cerr << "Accept failed" << endl; exit(1); } /////////////// // FORK MODE // /////////////// if (mode == FORK) { doItWithFork(sd2); // deal with child int pid; int status; struct rusage ruse; while ((pid = wait3(&status, WNOHANG, &ruse)) > 0); } ///////////////// // THREAD MODE // ///////////////// if (mode == THREAD) { doItWithThreads(sd2); // check to see if any threads are complete, by checking if they // sent any 'mail' checkMail(); } ///////////////// // SERIAL MODE // ///////////////// if (mode == DEFAULT) doItAsDefault(sd2); } }