Browse Source

Initial commit (base multithreading server)

Ivan Arkhipov 5 years ago
commit
fbed38cc7e
1 changed files with 387 additions and 0 deletions
  1. 387 0
      main.c

+ 387 - 0
main.c

@@ -0,0 +1,387 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <time.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <pthread.h>
+#include <signal.h>
+
+#define SERVER_PORT 12345
+#define MAX_CONNECTIONS 1000
+
+#define LOCK_FILE_PATH "/tmp/pid.lock"
+#define m_string char*
+
+/*
+Client
+    socket()
+  ? bind()    ?
+    connect()
+
+    ----------------
+    write()   | read()
+    send()    | recv()
+    sendto()  | recvfrom()
+    writev()  | readv()
+    sendmsg() | recvmsg()
+    ----------------
+
+    close()
+
+Server
+    socket()
+    bind()
+    listen()
+    accept()
+
+    ----------------
+    write()   | read()
+    send()    | recv()
+    sendto()  | recvfrom()
+    writev()  | readv()
+    sendmsg() | recvmsg()
+    ----------------
+
+    close()
+*/
+
+typedef enum {
+    eHTTP_UNKNOWN = 0,
+    eHTTP_CONNECT,
+    eHTTP_DELETE,
+    eHTTP_GET,
+    eHTTP_HEAD,
+    eHTTP_OPTIONS,
+    eHTTP_PATCH,
+    eHTTP_POST,
+    eHTTP_PUT,
+    eHTTP_TRACE
+} eHTTPMethod;
+
+typedef struct {
+    eHTTPMethod type;
+    char path[255];
+} sHTTPHeader;
+
+typedef struct {
+    struct strList *next;
+    char *cont;
+} strList;
+
+typedef struct {
+    void *access_semaphore;
+    char login[256];
+    char password[256];
+    char bound_ip[64];
+    char message[4096];
+} dbElem;
+
+typedef struct {
+    int sockd;
+    struct sockaddr_in client_sockaddr;
+    int client_sockaddr_size;
+} clientData;
+
+// DAEMON BASE FUNCTIONS
+
+// Starts server daemon, returns 1 on success, otherwise 0
+void start_server();
+
+// Stops server daemon, returns 1 on success, otherwise 0
+void stop_server();
+
+// Shows help and returns (is used if no parameter specified)
+void show_help();
+
+// Runs server event loop (called inside daemon)
+void process_server();
+
+
+// SERVER BASE FUNCTIONS
+
+// Creates socket, binds it to defined port and makes ready to listen for connections.
+int create_socket();
+
+// Opens (and creates, if necessary) file for user data and maps it to pointer in shared mode via memory-mapping.
+void *open_database();
+
+// Creates worker subprocess for connection, which lives while connection is established and communicates with client.
+// Returns worker pid to parent soon after creation (non-blocking communication).
+int process_request(int client_d, struct sockaddr *addr, void *database);
+
+
+// WORKER BASE FUNCTIONS
+
+// Waits until user request, then returns path, client_ip and header information
+void receive_request(m_string*path, m_string*client_ip, m_string*http_header);
+
+// Checks if user ip is bound to any login in database. Returns username, if bound and nullptr - if not.
+m_string check_user_authorisation(m_string client_ip);
+
+// Tries to update user message. Returns 1, if successfull, or 0 - if not;
+int update_user_message(m_string username, m_string msg);
+
+void send_404_not_found(int sockd);
+
+void send_401_not_authorised(int sockd);
+
+void send_403_forbidden(int sockd);
+
+void send_200_user_message(int sockd);
+
+void send_error_invalid_message(int sockd);
+
+// WORKER ADDITIONAL FUNCTIONS
+
+// Sends message
+void send_message(int sockd, const m_string msg);
+
+// Returns client address depending on ipv4/ipv6 protocol
+void *get_client_addr(struct sockaddr *);
+
+// Parses http request
+void* handle_request(void* data);
+
+void parse_http_request(const char *, sHTTPHeader *);
+
+// ============================================================================================= //
+
+int main(int argc, char** argv) {
+    if (argc < 2) {
+        show_help();
+        return 0;
+    }
+    if (strcmp(argv[1], "start") == 0) {
+        start_server();
+        return 0;
+    } else if (strcmp(argv[1], "stop") == 0) {
+        stop_server();
+        return 0;
+    } else if (strcmp(argv[1], "help") == 0) {
+        show_help();
+        return 0;
+    } else {
+        show_help();
+        return 0;
+    }
+}
+
+void start_server() {
+    FILE *lock_file = fopen(LOCK_FILE_PATH, "r");
+    if (lock_file) {
+        fprintf(stderr, "Error: seems like server is already running!\nStop it before starting the new one!\n");
+        return;
+    }
+
+    pid_t pid = fork();
+
+    if (pid == -1) {
+        fprintf(stderr, "Error: cannot create server! (fork exited with error: %s)\n", strerror(errno));
+        return;
+    } else if (pid == 0) {
+        process_server();
+        return;
+    } else {
+        printf("Server started with pid = %d\n", pid);
+        lock_file = fopen(LOCK_FILE_PATH, "w");
+        fprintf(lock_file, "%d", pid);
+        fclose(lock_file);
+        return;
+    }
+}
+
+void stop_server() {
+    FILE *lock_file = fopen(LOCK_FILE_PATH, "r");
+    if (!lock_file) {
+        fprintf(stderr, "Error: cannot stop server (no running server found)!\n");
+        return;
+    }
+
+    int pid;
+    if (fscanf(lock_file, "%d", &pid) != 1) {
+        fprintf(stderr, "Error: cannot stop server (pid read error)!\n");
+        fclose(lock_file);
+        return;
+    }
+
+    if (kill(pid, SIGTERM) != 0) {
+        fprintf(stderr, "Warning: server pid is incorrect, server might be already stopped (exited), but lock file still exists.\n");
+    }
+
+    fclose(lock_file);
+    if (remove(LOCK_FILE_PATH) != 0) {
+        fprintf(stderr, "Error: cannot remove server lock file (%s), but server was stopped...\n", LOCK_FILE_PATH);
+    } else {
+        printf("Server stopped successfully.\n");
+    }
+}
+
+void show_help() {
+    printf("Using: server <COMMAND>\n"
+           "\n"
+           "<COMMAND> values:\n"
+           "start - starts server daemon, if there's no one already started.\n"
+           "\n"
+           "stop - stops running server daemon if there's one.\n"
+           "\n"
+           "help - shows this help.\n"
+           "");
+}
+
+void * get_client_addr(struct sockaddr * sa) {
+    if (sa -> sa_family == AF_INET) {
+        return &(((struct sockaddr_in * ) sa) -> sin_addr);
+    }
+
+    return &(((struct sockaddr_in6 * ) sa) -> sin6_addr);
+}
+
+void process_server() {
+    int sock = create_socket();
+
+    if (sock < 0) {
+        fprintf(stderr, "Server: Error, cannot create socket!\n");
+        return;
+    }
+
+    printf("Server: server created and listening on port %d.\n", SERVER_PORT);
+
+    while (1) {
+        clientData *data = malloc(sizeof(clientData)); // would be freed in thread after its finishing
+        data->sockd = accept(sock, (struct sockaddr *) &data->client_sockaddr, &data->client_sockaddr_size);
+
+        pthread_t thread_id;
+        pthread_attr_t attr;
+        pthread_attr_init(&attr);
+
+        printf("Server: got new connection, creating worker thread.\n");
+        pthread_create(&thread_id, &attr, handle_request, data);
+        printf("Server: created worker thread %d.\n", thread_id);
+    }
+}
+
+int create_socket() {
+    printf("Creating socket\n");
+    int sock = socket(PF_INET, SOCK_STREAM, 0);
+
+    int on = 1;
+    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+    /* initialize the server's sockaddr */
+    struct sockaddr_in server_sockaddr;
+    memset(&server_sockaddr, 0, sizeof(server_sockaddr));
+    server_sockaddr.sin_family = AF_INET;
+    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+    server_sockaddr.sin_port = htons(SERVER_PORT);
+
+    printf("Binding socket %d to sockaddr %p with size %d\n", sock, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr));
+    int bind_result = bind(sock, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr));
+    if (bind_result < 0) {
+        fprintf(stderr, "Server: Error: bind failed!");
+        return -1;
+    }
+
+    listen(sock, MAX_CONNECTIONS);
+
+    return sock;
+}
+
+void* handle_request(void* data) {
+
+    clientData* client_data = data;
+
+    char ip[INET_ADDRSTRLEN];
+    inet_ntop(AF_INET, get_client_addr((struct sockaddr *) &client_data->client_sockaddr), ip, sizeof(ip));
+
+    printf("Worker %d: Established connection with %s beginning work.\n", pthread_self(), ip);
+
+    const int request_buffer_size = 65536;
+    char request[request_buffer_size];
+
+    int bytes_recvd = recv(client_data->sockd, request, request_buffer_size - 1, 0);
+
+    if (bytes_recvd < 0) {
+        fprintf(stderr, "error recv: %s\n", strerror(errno));
+        return NULL;
+    }
+    request[bytes_recvd] = '\0';
+
+    printf("request:\n%s\n", request);
+
+    sHTTPHeader req;
+    parse_http_request(request, &req);
+
+    if (req.type == eHTTP_GET) {
+        send_message(client_data->sockd, "Hello! GET received...");
+    } else if (req.type == eHTTP_POST) {
+        send_message(client_data->sockd, "Hello! POST received...");
+    } else {
+        send_404_not_found(client_data->sockd);
+    }
+
+    close(client_data->sockd);
+    printf("Worker %d: Finished.\n", pthread_self());
+    return NULL;
+}
+
+void parse_http_request(const char *apstrRequest, sHTTPHeader *apHeader) {
+    int type_length = 0;
+    char type[255] = {0};
+    int index = 0;
+
+    apHeader->type = eHTTP_UNKNOWN;
+
+    sscanf(&apstrRequest[index], "%s", type);
+
+    type_length = strlen(type);
+    if (!strcmp(type, "GET")) {
+        apHeader->type = eHTTP_GET;
+        index += type_length + 1;
+        sscanf(&apstrRequest[index], "%s", apHeader->path);
+    } else {
+        if (!strcmp(type, "POST")) {
+            apHeader->type = eHTTP_POST;
+            char *pch = strstr(apstrRequest, "\r\n\r\n");
+            pch += 4;
+            strcpy(apHeader->path, pch);
+        }
+    }
+}
+
+void send_message(int aSock, const char *apstrMessage) {
+    char buffer[65536] = {0};
+
+    strcat(buffer, "HTTP/1.1 200 OK\n\n");
+    strcat(buffer, "<h1>");
+    strcat(buffer, apstrMessage);
+    strcat(buffer, "</h1>");
+
+    int len = strlen(buffer);
+    send(aSock, buffer, len, 0);
+}
+
+void send_404_not_found(int sockd) {
+    const char *buffer = "HTTP/1.1 404 \n\n";
+    int len = strlen(buffer);
+    send(sockd, buffer, len, 0);
+}
+
+
+// server: got connection from 127.0.0.1
+// request:
+// GET /index.html HTTP/1.1
+// Host: localhost:3490
+// Connection: keep-alive
+// Upgrade-Insecure-Requests: 1
+// User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/68.0.3440.75 Chrome/68.0.3440.75 Safari/537.36
+// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
+// Accept-Encoding: gzip, deflate, br
+// Accept-Language: en-US,en;q=0.9,ru;q=0.8