diff options
| author | ckrinitsin <101062646+ckrinitsin@users.noreply.github.com> | 2025-03-21 22:15:01 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-21 22:15:01 +0100 |
| commit | 5fca08baa98058508c743101dcf10cd1b178ec7a (patch) | |
| tree | 9346b38f25ab5880af3f40e49ba1e64c4f7e0ca2 | |
| parent | 2a22b4123dce661b0500dc07012d61215bdce161 (diff) | |
| parent | 7212db13b9013aa15673ae65da65eeaf97ee0d12 (diff) | |
| download | BT-Programming-Assignment-5fca08baa98058508c743101dcf10cd1b178ec7a.tar.gz BT-Programming-Assignment-5fca08baa98058508c743101dcf10cd1b178ec7a.zip | |
Merge pull request #4 from ckrinitsin/client-input
Client input
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | src/client/client.cpp | 152 | ||||
| -rw-r--r-- | src/client/client.h | 83 | ||||
| -rw-r--r-- | src/client/main.cpp | 114 | ||||
| -rw-r--r-- | src/common/shared_memory.h | 19 | ||||
| -rw-r--r-- | src/server/hashtable.h | 78 | ||||
| -rw-r--r-- | src/server/main.cpp | 2 | ||||
| -rw-r--r-- | src/server/shared_memory_server.h | 84 |
8 files changed, 405 insertions, 129 deletions
diff --git a/Makefile b/Makefile index b526fed..7c250ef 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ CXXFLAGS := -std=c++23 -Wall -Wextra -Wpedantic -O3 -Isrc/common all: server client server: src/server/main.cpp ${CXX} ${CXXFLAGS} $^ -o $@ -client: src/client/main.cpp +client: src/client/main.cpp src/client/client.cpp ${CXX} ${CXXFLAGS} $^ -o $@ .PHONY: clean 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; }; |