about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorckrinitsin <101062646+ckrinitsin@users.noreply.github.com>2025-03-21 22:15:01 +0100
committerGitHub <noreply@github.com>2025-03-21 22:15:01 +0100
commit5fca08baa98058508c743101dcf10cd1b178ec7a (patch)
tree9346b38f25ab5880af3f40e49ba1e64c4f7e0ca2 /src
parent2a22b4123dce661b0500dc07012d61215bdce161 (diff)
parent7212db13b9013aa15673ae65da65eeaf97ee0d12 (diff)
downloadBT-Programming-Assignment-5fca08baa98058508c743101dcf10cd1b178ec7a.tar.gz
BT-Programming-Assignment-5fca08baa98058508c743101dcf10cd1b178ec7a.zip
Merge pull request #4 from ckrinitsin/client-input
Client input
Diffstat (limited to 'src')
-rw-r--r--src/client/client.cpp152
-rw-r--r--src/client/client.h83
-rw-r--r--src/client/main.cpp114
-rw-r--r--src/common/shared_memory.h19
-rw-r--r--src/server/hashtable.h78
-rw-r--r--src/server/main.cpp2
-rw-r--r--src/server/shared_memory_server.h84
7 files changed, 404 insertions, 128 deletions
diff --git a/src/client/client.cpp b/src/client/client.cpp
new file mode 100644
index 0000000..cbaddeb
--- /dev/null
+++ b/src/client/client.cpp
@@ -0,0 +1,152 @@
+#include "client.h"
+#include "shared_memory.h"
+#include <optional>
+
+Client::Client()
+{
+    shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);
+    if (shm_fd == -1) {
+        std::cout << "Server not running" << '\n';
+        exit(-1);
+    }
+
+    shared_memory =
+        (SharedMemory*)mmap(0, sizeof(SharedMemory), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
+}
+
+Client::~Client()
+{
+    munmap(shared_memory, sizeof(SharedMemory));
+    close(shm_fd);
+}
+
+void Client::start_client()
+{
+    while (true) {
+        char operation;
+        int k, v;
+        int index = -1;
+        std::cout << "Choose the operation (i: Insert, g: Get, r: Remove, p: Print, e: Exit)"
+                  << '\n';
+        std::cin >> operation;
+
+        switch (operation) {
+        case 'e':
+            return;
+
+        case 'i':
+            std::cout << "Insert: Enter the k-v pair(<int> <int>):" << '\n';
+            if (!(std::cin >> k >> v)) {
+                std::cout << "Invalid input" << '\n';
+                std::cin.clear();
+                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
+                break;
+            };
+            index = send_request(
+                shared_memory, INSERT, std::optional(serialize(k)), std::optional(serialize(v)));
+            break;
+
+        case 'g': {
+            std::cout << "Get: Enter the key(<int>):" << '\n';
+            if (!(std::cin >> k)) {
+                std::cout << "Invalid input" << '\n';
+                std::cin.clear();
+                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
+                break;
+            };
+            index = send_request(shared_memory, GET, std::optional(serialize(k)), std::nullopt);
+            break;
+        }
+
+        case 'r':
+            std::cout << "Remove: Enter key(<int>):" << '\n';
+            if (!(std::cin >> k)) {
+                std::cout << "Invalid input" << '\n';
+                std::cin.clear();
+                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
+                break;
+            };
+            index = send_request(shared_memory, DELETE, std::optional(serialize(k)), std::nullopt);
+            break;
+
+        case 'p':
+            index = send_request(shared_memory, PRINT, std::nullopt, std::nullopt);
+            break;
+
+        default:
+            break;
+        }
+
+        if (index != -1) {
+            std::string response = process_respond(shared_memory, index);
+            std::cout << response << '\n';
+        }
+        std::cout << '\n';
+    }
+}
+
+bool Client::request_processed(SharedMemory* shared_memory, int index)
+{
+    if (index >= QUEUE_SIZE || index < 0) {
+        return false;
+    }
+
+    if (shared_memory->full) {
+        return false;
+    }
+    if (shared_memory->tail == shared_memory->head && !shared_memory->full) {
+        return true;
+    }
+
+    for (int i = shared_memory->head - 1; i != shared_memory->tail; i = (i - 1) % QUEUE_SIZE) {
+        if (i == index) {
+            return false;
+        }
+    }
+
+    return shared_memory->tail != index;
+}
+
+int Client::send_request(
+    SharedMemory* shared_memory,
+    Operations type,
+    std::optional<const std::string> k,
+    std::optional<const std::string> v)
+{
+    int index;
+
+    pthread_mutex_lock(&shared_memory->mutex);
+
+    while (shared_memory->full) {
+        pthread_cond_wait(&shared_memory->cond_var, &shared_memory->mutex);
+    }
+
+    index = shared_memory->head;
+    Request* request = &shared_memory->request[index];
+    request->type = type;
+    strncpy(request->key, k.value_or("null").c_str(), MAX_KEY_SIZE);
+    strncpy(request->value, v.value_or("null").c_str(), MAX_VALUE_SIZE);
+    shared_memory->head = (1 + shared_memory->head) % QUEUE_SIZE;
+    shared_memory->full = shared_memory->head == shared_memory->tail;
+    pthread_cond_signal(&shared_memory->cond_var);
+
+    pthread_mutex_unlock(&shared_memory->mutex);
+
+    return index;
+}
+
+std::string Client::process_respond(SharedMemory* shared_memory, int index)
+{
+    if (index >= QUEUE_SIZE || index < 0) {
+        return std::string();
+    }
+
+    pthread_mutex_lock(&shared_memory->mutex);
+
+    while (!request_processed(shared_memory, index)) {
+        pthread_cond_wait(&shared_memory->cond_var, &shared_memory->mutex);
+    }
+    std::string result(shared_memory->request[index].response);
+    pthread_mutex_unlock(&shared_memory->mutex);
+    return result;
+}
diff --git a/src/client/client.h b/src/client/client.h
new file mode 100644
index 0000000..bc2d702
--- /dev/null
+++ b/src/client/client.h
@@ -0,0 +1,83 @@
+#pragma once
+
+#include <cstring>
+#include <fcntl.h>
+#include <iostream>
+#include <optional>
+#include <pthread.h>
+#include <string>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <utility>
+
+#include "shared_memory.h"
+
+/**
+ * @class Client
+ * @brief Represents the client, which performs actions on the hashtable of the server.
+ */
+class Client {
+public:
+    /**
+     * @brief Constructs a new client and opens the shared memory.
+     */
+    Client();
+
+    /**
+     * @brief Unmaps the shared memory.
+     */
+    ~Client();
+
+    /**
+     * @brief Main client program.
+     *
+     * @details The user can choose the operation and the arguments, which will be sent to the
+     * server.
+     */
+    void start_client();
+
+private:
+    /**
+     * @brief Memory which is shared with the server.
+     */
+    SharedMemory* shared_memory;
+
+    /**
+     * @brief File descriptor for the shared memory, used to unmap and close the memory at the end.
+     */
+    int shm_fd;
+
+    /**
+     * @brief Sends a request to the server.
+     *
+     * @param shared_memory The memory to use.
+     * @param type The type of the operation the server has to process.
+     * @param k First potential argument of the request, represents the key.
+     * @param v Second potential argument of the request, represent the value.
+     * @return int The index of the request in the circular-buffer, so we can access it again for
+     * processing the respond.
+     */
+    int send_request(
+        SharedMemory* shared_memory,
+        Operations type,
+        std::optional<const std::string> k,
+        std::optional<const std::string> v);
+
+    /**
+     * @brief Determines if the request was processed by the server.
+     *
+     * @param shared_memory The memory to use.
+     * @param index The index of the request in the circular-buffer.
+     * @return bool The request was processed by the server.
+     */
+    bool request_processed(SharedMemory* shared_memory, int index);
+
+    /**
+     * @brief Processes the respond of the server.
+     *
+     * @param shared_memory The memory to use.
+     * @param index The index of the request in the circular-buffer.
+     * @return std::string The response of the server as a string.
+     */
+    std::string process_respond(SharedMemory* shared_memory, int index);
+};
diff --git a/src/client/main.cpp b/src/client/main.cpp
index 6dfd48f..7e61ffe 100644
--- a/src/client/main.cpp
+++ b/src/client/main.cpp
@@ -1,115 +1,7 @@
-#include "shared_memory.h"
-#include <cstring>
-#include <fcntl.h>
-#include <iostream>
-#include <pthread.h>
-#include <sys/mman.h>
-#include <unistd.h>
-#include <utility>
-
-bool request_processed(SharedMemory* shared_memory, int index)
-{
-    if (shared_memory->full) {
-        return false;
-    }
-    if (shared_memory->tail == shared_memory->head && !shared_memory->full) {
-        return true;
-    }
-
-    for (int i = shared_memory->head - 1; i != shared_memory->tail; i = (i - 1) % QUEUE_SIZE) {
-        if (i == index) {
-            return false;
-        }
-    }
-
-    return shared_memory->tail != index;
-}
-
-void send_request(
-    SharedMemory* shared_memory,
-    Operations type,
-    std::pair<const std::string&, const std::string&> arguments)
-{
-    int index;
-
-    pthread_mutex_lock(&shared_memory->mutex);
-
-    if (shared_memory->full) {
-        std::cout << "Queue is full" << '\n';
-        pthread_cond_wait(&shared_memory->cond_var, &shared_memory->mutex);
-        return;
-    }
-
-    index = shared_memory->head;
-    Request* request = &shared_memory->request[index];
-    request->type = type;
-    strncpy(request->key, serialize(arguments.first).c_str(), MAX_KEY_SIZE);
-    strncpy(request->value, serialize(arguments.second).c_str(), MAX_VALUE_SIZE);
-    shared_memory->head = (1 + shared_memory->head) % QUEUE_SIZE;
-    shared_memory->full = shared_memory->head == shared_memory->tail;
-    pthread_cond_signal(&shared_memory->cond_var);
-
-    pthread_mutex_unlock(&shared_memory->mutex);
-
-    std::cout << "Command sent" << '\n';
-
-    if (type == Operations::GET) {
-        pthread_mutex_lock(&shared_memory->mutex);
-
-        while (!request_processed(shared_memory, index)) {
-            pthread_cond_wait(&shared_memory->cond_var, &shared_memory->mutex);
-        }
-        std::string result(shared_memory->request[index].response);
-        pthread_mutex_unlock(&shared_memory->mutex);
-        std::cout << "Result: " << result << std::endl;
-    }
-}
+#include "client.h"
 
 int main()
 {
-    int shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);
-    if (shm_fd == -1) {
-        std::cout << "Server not running" << '\n';
-        return -1;
-    }
-
-    SharedMemory* shared_memory =
-        (SharedMemory*)mmap(0, sizeof(SharedMemory), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
-
-    std::cout << "Start inserting.." << '\n';
-    send_request(shared_memory, INSERT, std::pair(serialize(3), serialize(3)));
-
-    std::cout << "Start inserting.." << '\n';
-    send_request(shared_memory, INSERT, std::pair(serialize(4), serialize(5)));
-
-    std::cout << "Start inserting.." << '\n';
-    send_request(shared_memory, INSERT, std::pair(serialize(6), serialize(6)));
-
-    std::cout << "Start inserting.." << '\n';
-    send_request(shared_memory, INSERT, std::pair(serialize(7), serialize(3)));
-
-    std::cout << "Start inserting.." << '\n';
-    send_request(shared_memory, INSERT, std::pair(serialize(8), serialize(3)));
-
-    std::cout << "Start inserting.." << '\n';
-    send_request(shared_memory, INSERT, std::pair(serialize(9), serialize(3)));
-
-    std::cout << "Start inserting.." << '\n';
-    send_request(shared_memory, INSERT, std::pair(serialize(10), serialize(3)));
-
-    std::cout << "Start inserting.." << '\n';
-    send_request(shared_memory, INSERT, std::pair(serialize(11), serialize(3)));
-
-    std::cout << "Start inserting.." << '\n';
-    send_request(shared_memory, INSERT, std::pair(serialize(12), serialize(3)));
-
-    std::cout << "Start inserting.." << '\n';
-    send_request(shared_memory, INSERT, std::pair(serialize(13), serialize(4)));
-
-    std::cout << "Start PRINTING.." << '\n';
-    send_request(shared_memory, PRINT, std::pair(serialize(0), serialize(0)));
-
-    munmap(shared_memory, sizeof(SharedMemory));
-    close(shm_fd);
-    return 0;
+    Client client;
+    client.start_client();
 }
diff --git a/src/common/shared_memory.h b/src/common/shared_memory.h
index 70554e4..fe101ea 100644
--- a/src/common/shared_memory.h
+++ b/src/common/shared_memory.h
@@ -9,8 +9,14 @@
 #define MAX_KEY_SIZE 64
 #define MAX_VALUE_SIZE 128
 
+/**
+ * @brief Possible operations on the hashtable.
+ */
 enum Operations { INSERT, DELETE, GET, PRINT };
 
+/**
+ * @brief One request constists out of the operation, the arguments and the response.
+ */
 struct Request {
     Operations type;
     char key[MAX_KEY_SIZE];
@@ -18,6 +24,13 @@ struct Request {
     char response[MAX_VALUE_SIZE];
 };
 
+/**
+ * @brief The shared memory between client and server.
+ *
+ * @details The shared memory consists out of:
+ * - A circular-buffer, to de- and enqueue multiple request at once.
+ * - A mutex with a conditional variable, to ensure concurrency of the buffer.
+ */
 struct SharedMemory {
     Request request[QUEUE_SIZE];
     pthread_mutex_t mutex;
@@ -28,6 +41,9 @@ struct SharedMemory {
     bool full;
 };
 
+/**
+ * @brief Converts a generic type to a stringstream, so that it can be saved in the shared memory.
+ */
 template <typename T>
 std::string serialize(const T& data)
 {
@@ -36,6 +52,9 @@ std::string serialize(const T& data)
     return oss.str();
 }
 
+/**
+ * @brief Converts a stringstream to a generic type, so that value in the shared memory can be read.
+ */
 template <typename T>
 T deserialize(const std::string& str)
 {
diff --git a/src/server/hashtable.h b/src/server/hashtable.h
index 786d8f5..e09fcd4 100644
--- a/src/server/hashtable.h
+++ b/src/server/hashtable.h
@@ -6,11 +6,21 @@
 #include <mutex>
 #include <optional>
 #include <shared_mutex>
+#include <sstream>
 #include <vector>
 
+/**
+ * @class HashTable
+ * @brief Represents a generic hashtable with simple operations.
+ */
 template <typename K, typename V>
 class HashTable {
 public:
+    /**
+     * @brief Constructs a new Hashtable.
+     *
+     * @param size The number of buckets of the table.
+     */
     HashTable(size_t size)
         : size { size }
         , table(size)
@@ -18,6 +28,13 @@ public:
     {
     }
 
+    /**
+     * @brief Insert a kv-pair into the hashtable.
+     *
+     * @param key The key to determine the bucket.
+     * @param value The value to insert.
+     * @return bool Successful insert of the pair.
+     */
     bool insert(K key, V value)
     {
         size_t index = get_bucket_index(key);
@@ -34,6 +51,12 @@ public:
         return true;
     }
 
+    /**
+     * @brief Gets the value which corresponds to the key.
+     *
+     * @param key The key to look for.
+     * @return std::optional The value, if the key could be found.
+     */
     std::optional<V> get(K key)
     {
         size_t index = get_bucket_index(key);
@@ -49,6 +72,12 @@ public:
         return std::optional<V>();
     }
 
+    /**
+     * @brief Removes the kv-pair which corresponds to the key.
+     *
+     * @param key The key to look for.
+     * @return bool The pair could be removed successfully.
+     */
     bool remove(K key)
     {
         size_t index = get_bucket_index(key);
@@ -65,29 +94,57 @@ public:
         return false;
     }
 
-    void print()
+    /**
+     * @brief Constructs a string representation of the hashtable.
+     *
+     * @return std::string The string of the hashtable.
+     */
+    std::string string()
     {
+        std::ostringstream output;
+
         size_t index { 0 };
         for (auto bucket : table) {
-            std::cout << "Bucket " << index << ": [";
+            output << "Bucket " << index << ": [";
             std::shared_lock<std::shared_mutex> lock(bucket_mutexes.at(index));
             for (auto pair : bucket) {
-                std::cout << "(" << pair.first << ", " << pair.second << ")";
+                output << "(" << pair.first << ", " << pair.second << ")";
             }
-            std::cout << "]" << "\n";
+            output << "]" << "\n";
             ++index;
         }
+
+        return output.str();
     }
 
 private:
+    /**
+     * @brief The number of buckets.
+     */
     size_t size;
 
+    /**
+     * @brief The hashtable.
+     */
     std::vector<std::list<std::pair<K, V>>> table;
 
+    /**
+     * @brief A mutex for every button.
+     */
     std::vector<std::shared_mutex> bucket_mutexes;
 
+    /**
+     * @brief The hashfunction to use for the bucket determination.
+     */
     std::hash<K> hash_function;
 
+    /**
+     * @brief Finds the kv-pair inside a bucket.
+     *
+     * @param list The bucket.
+     * @param key The key to look for.
+     * @return auto The iterator element, which points to the kv-pair or list.end().
+     */
     auto bucket_find_key(std::list<std::pair<K, V>>& list, K key)
     {
         return std::find_if(list.begin(), list.end(), [&key](const std::pair<K, V>& pair) {
@@ -95,10 +152,23 @@ private:
         });
     }
 
+    /**
+     * @brief Checks if the bucket contains the key.
+     *
+     * @param list The bucket.
+     * @param key The key to look for.
+     * @return bool The bucket contains the key.
+     */
     bool bucket_contains_key(std::list<std::pair<K, V>>& list, K key)
     {
         return list.begin() != list.end() && bucket_find_key(list, key) != list.end();
     }
 
+    /**
+     * @brief Uses the hashfunction and the key to determine, which bucket to use.
+     *
+     * @param key The key.
+     * @return size_t The index of the bucket.
+     */
     size_t get_bucket_index(K key) { return hash_function(key) % size; }
 };
diff --git a/src/server/main.cpp b/src/server/main.cpp
index 424326f..d724bd5 100644
--- a/src/server/main.cpp
+++ b/src/server/main.cpp
@@ -20,7 +20,7 @@ int main(int argc, char* argv[])
         return 1;
     }
 
-    SharedMemoryServer<int, std::string> shm(size);
+    Server<int, int> shm(size);
 
     shm.process_requests();
 
diff --git a/src/server/shared_memory_server.h b/src/server/shared_memory_server.h
index 74abedf..4580d1a 100644
--- a/src/server/shared_memory_server.h
+++ b/src/server/shared_memory_server.h
@@ -8,10 +8,20 @@
 #include <sys/mman.h>
 #include <unistd.h>
 
+/**
+ * @class Server
+ * @brief Represents the server, which performs operations on the hashtable based on the requests of
+ * the client.
+ */
 template <typename K, typename V>
-class SharedMemoryServer {
+class Server {
 public:
-    SharedMemoryServer(size_t size)
+    /**
+     * @brief Constructs a new hashtable and initializes a shared memory buffer.
+     *
+     * @param size The number of buckets in the hashtable.
+     */
+    Server(size_t size)
         : hash_table(size)
     {
         shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
@@ -34,13 +44,21 @@ public:
         pthread_cond_init(&shared_memory->cond_var, &cond_attr);
     }
 
-    ~SharedMemoryServer()
+    /**
+     * @brief Unmaps and unlinks the shared memory.
+     */
+    ~Server()
     {
         munmap(shared_memory, sizeof(SharedMemory));
         close(shm_fd);
         shm_unlink(SHM_NAME);
     }
 
+    /**
+     * @brief The main loop of the server.
+     *
+     * @details The server checks the shared memory for new requests and executes them.
+     */
     void process_requests()
     {
         while (true) {
@@ -57,40 +75,82 @@ public:
 
             switch (request->type) {
             case INSERT:
-                std::cout << "Inserting" << '\n';
-                hash_table.insert(key, value);
+                std::cout << "Insert operation" << '\n';
+                if (hash_table.insert(key, value)) {
+                    strncpy(
+                        request->response,
+                        serialize<std::string>("Inserted successfully").c_str(),
+                        MAX_VALUE_SIZE);
+                } else {
+                    strncpy(
+                        request->response,
+                        serialize<std::string>("Key is already available").c_str(),
+                        MAX_VALUE_SIZE);
+                }
                 break;
+
             case GET: {
-                std::cout << "Getting" << '\n';
+                std::cout << "Get operation" << '\n';
                 hash_table.insert(key, value);
                 std::optional<V> result = hash_table.get(key);
                 if (result.has_value()) {
                     std::string response = serialize<V>(result.value());
                     strncpy(request->response, response.c_str(), MAX_VALUE_SIZE);
-                    pthread_cond_signal(&shared_memory->cond_var);
+                } else {
+                    strncpy(
+                        request->response,
+                        serialize<std::string>("Couldn't get any value").c_str(),
+                        MAX_VALUE_SIZE);
                 }
                 break;
             }
+
             case DELETE:
-                std::cout << "Deleting" << '\n';
-                hash_table.remove(key);
+                std::cout << "Remove operation" << '\n';
+                if (hash_table.remove(key)) {
+                    strncpy(
+                        request->response,
+                        serialize<std::string>("Key successfully deleted").c_str(),
+                        MAX_VALUE_SIZE);
+                } else {
+                    strncpy(
+                        request->response,
+                        serialize<std::string>("Couldn't find the key").c_str(),
+                        MAX_VALUE_SIZE);
+                }
                 break;
+
             case PRINT:
-                std::cout << "Printing" << '\n';
-                hash_table.print();
+                std::cout << "Print operation" << '\n';
+                strncpy(
+                    request->response,
+                    serialize<std::string>(hash_table.string()).c_str(),
+                    MAX_VALUE_SIZE);
                 break;
+
             default:
                 break;
             }
             shared_memory->tail = (1 + shared_memory->tail) % QUEUE_SIZE;
             shared_memory->full = false;
+            pthread_cond_signal(&shared_memory->cond_var);
             pthread_mutex_unlock(&shared_memory->mutex);
         }
     }
 
 private:
+    /**
+     * @brief The hashtable.
+     */
     HashTable<K, V> hash_table;
 
-    int shm_fd;
+    /**
+     * @brief Memory which is shared with the client.
+     */
     SharedMemory* shared_memory;
+
+    /**
+     * @brief File descriptor for the shared memory, used to unmap and close the memory at the end.
+     */
+    int shm_fd;
 };