Skip to content

Instantly share code, notes, and snippets.

@Lhy121125
Last active February 18, 2023 16:35
Show Gist options
  • Save Lhy121125/abdabf6622147c8d8a8099275a381a56 to your computer and use it in GitHub Desktop.
Save Lhy121125/abdabf6622147c8d8a8099275a381a56 to your computer and use it in GitHub Desktop.
A http server that allows client to connect to multiple ports concurrently
/*
* multi-server.c
*/
#include <stdio.h> /* for printf() and fprintf() */
#include <sys/socket.h> /* for socket(), bind(), and connect() */
#include <arpa/inet.h> /* for sockaddr_in and inet_ntoa() */
#include <stdlib.h> /* for atoi() and exit() */
#include <string.h> /* for memset() */
#include <unistd.h> /* for close() */
#include <time.h> /* for time() */
#include <netdb.h> /* for gethostbyname() */
#include <signal.h> /* for signal() */
#include <sys/stat.h> /* for stat() */
#include <pthread.h>
#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#define MAXPENDING 5 /* Maximum outstanding connection requests */
#define DISK_IO_BUF_SIZE 4096
#define N_THREADS 16
static pthread_t thread_pool[N_THREADS];
/*
* A message in a blocking queue
*/
struct message
{
int sock; // Payload, in our case a new client connection
struct message *next; // Next message on the list
};
/*
* This structure implements a blocking queue.
* If a thread attempts to pop an item from an empty queue
* it is blocked until another thread appends a new item.
*/
struct queue
{
pthread_mutex_t mutex; // mutex used to protect the queue
pthread_cond_t cond; // condition variable for threads to sleep on
struct message *first; // first message in the queue
struct message *last; // last message in the queue
unsigned int length; // number of elements on the queue
};
static void die(const char *message)
{
perror(message);
exit(1);
}
// initializes the members of struct queue
void queue_init(struct queue *q)
{
q->first = NULL;
q->last = NULL;
q->length = 0;
if (pthread_mutex_init(&(q->mutex), NULL) != 0)
die("mutex init failed");
if (pthread_cond_init(&(q->cond), NULL) != 0)
die("cond. var. init failed");
}
// deallocate and destroy everything in the queue
void queue_destroy(struct queue *q)
{
pthread_mutex_lock(&q->mutex);
struct message *cur_message = q->first;
while (cur_message != NULL)
{
struct message *temp = cur_message;
--q->length;
cur_message = cur_message->next;
free(temp);
}
pthread_mutex_destroy(&(q->mutex));
pthread_cond_destroy(&(q->cond));
}
// put a message into the queue and wake up workers if necessary
void queue_put(struct queue *q, int sock)
{
struct message *workq = (struct message *)malloc(sizeof(struct message));
workq->sock = sock;
workq->next = NULL;
pthread_mutex_lock(&(q->mutex));
// if it is empty
if (q->last == NULL)
{
q->first = workq;
q->last = workq;
if (pthread_cond_broadcast(&(q->cond)) != 0)
die("pthread broadcast failed");
}
else
{
q->last->next = workq;
q->last = workq;
}
q->length++;
pthread_mutex_unlock(&(q->mutex));
}
// take a socket descriptor from the queue; block if necessary
int queue_get(struct queue *q)
{
pthread_mutex_lock(&(q->mutex));
while (q->first == NULL)
{
pthread_cond_wait(&(q->cond), &(q->mutex));
}
struct message *current = q->first;
int res = current->sock;
q->first = q->first->next;
q->length--;
if (q->length == 0)
{
q->last = NULL;
}
free(current);
pthread_mutex_unlock(&(q->mutex));
return res;
}
// Use for debugging
// void queue_print(struct queue *q) {
// pthread_mutex_lock(&q->mutex);
// struct message *current = q->first;
// while (current != NULL) {
// printf("%d ", current->sock);
// current = current->next;
// }
// printf("\n");
// pthread_mutex_unlock(&q->mutex);
// }
/*
* Create a listening socket bound to the given port.
*/
static int createServerSocket(unsigned short port)
{
int servSock;
struct sockaddr_in servAddr;
/* Create socket for incoming connections */
if ((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
die("socket() failed");
/* Construct local address structure */
memset(&servAddr, 0, sizeof(servAddr)); /* Zero out structure */
servAddr.sin_family = AF_INET; /* Internet address family */
servAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Any incoming interface */
servAddr.sin_port = htons(port); /* Local port */
/* Bind to the local address */
if (bind(servSock, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0)
die("bind() failed");
/* Mark the socket so it will listen for incoming connections */
if (listen(servSock, MAXPENDING) < 0)
die("listen() failed");
return servSock;
}
/*
* A wrapper around send() that does error checking and logging.
* Returns -1 on failure.
*
* This function assumes that buf is a null-terminated string, so
* don't use this function to send binary data.
*/
ssize_t Send(int sock, const char *buf)
{
size_t len = strlen(buf);
ssize_t res = send(sock, buf, len, 0);
if (res != len)
{
perror("send() failed");
return -1;
}
else
return res;
}
/*
* HTTP/1.0 status codes and the corresponding reason phrases.
*/
static struct
{
int status;
char *reason;
} HTTP_StatusCodes[] = {
{200, "OK"},
{201, "Created"},
{202, "Accepted"},
{204, "No Content"},
{301, "Moved Permanently"},
{302, "Moved Temporarily"},
{304, "Not Modified"},
{400, "Bad Request"},
{401, "Unauthorized"},
{403, "Forbidden"},
{404, "Not Found"},
{500, "Internal Server Error"},
{501, "Not Implemented"},
{502, "Bad Gateway"},
{503, "Service Unavailable"},
{0, NULL} // marks the end of the list
};
static inline const char *getReasonPhrase(int statusCode)
{
int i = 0;
while (HTTP_StatusCodes[i].status > 0)
{
if (HTTP_StatusCodes[i].status == statusCode)
return HTTP_StatusCodes[i].reason;
i++;
}
return "Unknown Status Code";
}
/*
* Send HTTP status line followed by a blank line.
*/
static void sendStatusLine(int clntSock, int statusCode)
{
char buf[1000];
const char *reasonPhrase = getReasonPhrase(statusCode);
// print the status line into the buffer
sprintf(buf, "HTTP/1.0 %d ", statusCode);
strcat(buf, reasonPhrase);
strcat(buf, "\r\n");
// We don't send any HTTP header in this simple server.
// We need to send a blank line to signal the end of headers.
strcat(buf, "\r\n");
// For non-200 status, format the status line as an HTML content
// so that browers can display it.
if (statusCode != 200)
{
char body[1000];
sprintf(body,
"<html><body>\n"
"<h1>%d %s</h1>\n"
"</body></html>\n",
statusCode, reasonPhrase);
strcat(buf, body);
}
// send the buffer to the browser
Send(clntSock, buf);
}
/*
* Handle static file requests.
* Returns the HTTP status code that was sent to the browser.
*/
static int handleFileRequest(
const char *webRoot, const char *requestURI, int clntSock)
{
int statusCode;
FILE *fp = NULL;
// Compose the file path from webRoot and requestURI.
// If requestURI ends with '/', append "index.html".
char *file = (char *)malloc(strlen(webRoot) + strlen(requestURI) + 100);
if (file == NULL)
die("malloc failed");
strcpy(file, webRoot);
strcat(file, requestURI);
if (file[strlen(file) - 1] == '/')
{
strcat(file, "index.html");
}
// See if the requested file is a directory.
// Our server does not support directory listing.
struct stat st;
if (stat(file, &st) == 0 && S_ISDIR(st.st_mode))
{
statusCode = 403; // "Forbidden"
sendStatusLine(clntSock, statusCode);
goto func_end;
}
// If unable to open the file, send "404 Not Found".
fp = fopen(file, "rb");
if (fp == NULL)
{
statusCode = 404; // "Not Found"
sendStatusLine(clntSock, statusCode);
goto func_end;
}
// Otherwise, send "200 OK" followed by the file content.
statusCode = 200; // "OK"
sendStatusLine(clntSock, statusCode);
// send the file
size_t n;
char buf[DISK_IO_BUF_SIZE];
while ((n = fread(buf, 1, sizeof(buf), fp)) > 0)
{
if (send(clntSock, buf, n, 0) != n)
{
// send() failed.
// We log the failure, break out of the loop,
// and let the server continue on with the next request.
perror("\nsend() failed");
break;
}
}
// fread() returns 0 both on EOF and on error.
// Let's check if there was an error.
if (ferror(fp))
perror("fread failed");
func_end:
// clean up
free(file);
if (fp)
fclose(fp);
return statusCode;
}
// a static queue struct
static struct queue que;
void *thread_func(void *arg)
{
for (;;)
{
char line[1000];
char requestLine[1000];
int statusCode;
struct sockaddr_in clntAddr;
const char *webRoot = (char *)arg;
int clntSock;
char bufff[INET_ADDRSTRLEN];
clntSock = queue_get(&que);
FILE *clntFp = fdopen(clntSock, "r");
if (clntFp == NULL)
die("fdopen failed");
socklen_t clnt_addr_len = sizeof(clntAddr);
if (getpeername(clntSock, (struct sockaddr *)&clntAddr, &clnt_addr_len) == -1)
{
die("getpeername error");
}
/*
* Let's parse the request line.
*/
char *method = "";
char *requestURI = "";
char *httpVersion = "";
if (fgets(requestLine, sizeof(requestLine), clntFp) == NULL)
{
// socket closed - there isn't much we can do
statusCode = 400; // "Bad Request"
goto loop_end;
}
char *token_separators = "\t \r\n"; // tab, space, new line
char *buff;
method = strtok_r(requestLine, token_separators, &buff);
requestURI = strtok_r(NULL, token_separators, &buff);
httpVersion = strtok_r(NULL, token_separators, &buff);
char *extraThingsOnRequestLine = strtok_r(NULL, token_separators, &buff);
// fprintf(stderr,"we see the request URI: %s\n",requestURI);
// check if we have 3 (and only 3) things in the request line
if (!method || !requestURI || !httpVersion ||
extraThingsOnRequestLine)
{
statusCode = 501; // "Not Implemented"
sendStatusLine(clntSock, statusCode);
goto loop_end;
}
// we only support GET method
if (strcmp(method, "GET") != 0)
{
statusCode = 501; // "Not Implemented"
sendStatusLine(clntSock, statusCode);
goto loop_end;
}
// we only support HTTP/1.0 and HTTP/1.1
if (strcmp(httpVersion, "HTTP/1.0") != 0 &&
strcmp(httpVersion, "HTTP/1.1") != 0)
{
statusCode = 501; // "Not Implemented"
sendStatusLine(clntSock, statusCode);
goto loop_end;
}
// requestURI must begin with "/"
if (!requestURI || *requestURI != '/')
{
statusCode = 400; // "Bad Request"
sendStatusLine(clntSock, statusCode);
goto loop_end;
}
// make sure that the requestURI does not contain "/../" and
// does not end with "/..", which would be a big security hole!
int len = strlen(requestURI);
if (len >= 3)
{
char *tail = requestURI + (len - 3);
if (strcmp(tail, "/..") == 0 ||
strstr(requestURI, "/../") != NULL)
{
statusCode = 400; // "Bad Request"
sendStatusLine(clntSock, statusCode);
goto loop_end;
}
}
/*
* Now let's skip all headers.
*/
while (1)
{
if (fgets(line, sizeof(line), clntFp) == NULL)
{
// socket closed prematurely - there isn't much we can do
statusCode = 400; // "Bad Request"
goto loop_end;
}
if (strcmp("\r\n", line) == 0 || strcmp("\n", line) == 0)
{
// This marks the end of headers.
// Break out of the while loop.
break;
}
}
/*
* At this point, we have a well-formed HTTP GET request.
* Let's handle it.
*/
statusCode = handleFileRequest(webRoot, requestURI, clntSock);
loop_end:
/*
* Done with client request.
* Log it, close the client socket, and go back to accepting
* connection.
*/
fprintf(stderr, "%s \"%s %s %s\" %d %s\n",
inet_ntop(AF_INET, &clntAddr.sin_addr, bufff, INET_ADDRSTRLEN),
method,
requestURI,
httpVersion,
statusCode,
getReasonPhrase(statusCode));
// close the client socket
fclose(clntFp);
} // for (;;)
// fclose(clntFp);
// pthread_exit(0);
}
int main(int argc, char *argv[])
{
if (argc < 3)
{
fprintf(stderr,
"usage: %s <server_port> [<server_port> ...] <web_root>\n",
argv[0]);
exit(1);
}
int servSocks[32];
memset(servSocks, -1, sizeof(servSocks));
// Create server sockets for all ports we listen on
for (int i = 1; i < argc - 1; i++)
{
if (i >= (sizeof(servSocks) / sizeof(servSocks[0])))
die("Too many listening sockets");
servSocks[i - 1] = createServerSocket(atoi(argv[i]));
}
char *webRoot = argv[argc - 1];
// Initialize queue
queue_init(&que);
// Ignore SIGPIPE so that we don't terminate when we call
// send() on a disconnected socket.
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
die("signal() failed");
struct sockaddr_in clientname;
fd_set active_fd_set, read_fd_set;
// Precreate all the worker thread
for (int i = 0; i < N_THREADS; i++)
{
if (pthread_create(&thread_pool[i], NULL, thread_func, (void *)(webRoot)) != 0)
{
die("pthread error");
}
}
FD_ZERO(&active_fd_set);
// Set all the read_fd from servSocks
int maximum = 0;
for (int i = 0; i < 32; i++)
{
FD_SET(servSocks[i], &active_fd_set);
//>= vs >
if (servSocks[i] > maximum)
{
maximum = servSocks[i];
}
}
while (1)
{
read_fd_set = active_fd_set;
int numRdy;
while ((numRdy = select(maximum + 1, &read_fd_set, NULL, NULL, NULL)) < 0 &&
errno == EINTR)
{
fprintf(stderr, "Select return: %d \n", numRdy);
}
if (numRdy < 0)
{
die("select error");
}
int new;
// Go through all the possible file descriptor that are open for I/O
for (int i = 0; i < maximum; i++)
{
if (FD_ISSET(servSocks[i], &read_fd_set))
{
unsigned int size = sizeof(clientname);
new = accept(servSocks[i], (struct sockaddr *)&clientname, &size);
if (new < 0)
{
die("failed to accept");
}
queue_put(&que, new);
// for debugging
// fprintf(stderr,
// "Server: connect from host %s, port %hd.\n",
// inet_ntoa(clientname.sin_addr),
// ntohs(clientname.sin_port));
}
}
}
queue_destroy(&que);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment