From 51a309e55f4b519951cc131ef7d9e40c37026234 Mon Sep 17 00:00:00 2001 From: Francesco Date: Sat, 9 Nov 2024 19:58:58 +0100 Subject: [PATCH] initial hashmap code --- .clang-format | 2 + .vscode/settings.json | 2 +- README.md | 2 +- include/client.h | 8 +-- include/hashmap.h | 16 +++++ include/server.h | 15 ++-- include/utils.h | 5 +- notes/advanced-techniques.md | 2 +- notes/hash-table.md | 19 ++++++ notes/http-request.md | 11 +++ src/client.c | 20 +++--- src/hashmap.c | 9 +++ src/main.c | 1 + src/mainc.c | 2 +- src/server.c | 129 +++++++++++++++++++++-------------- src/utils.c | 16 ++--- 16 files changed, 172 insertions(+), 87 deletions(-) create mode 100644 include/hashmap.h create mode 100644 notes/hash-table.md create mode 100644 notes/http-request.md create mode 100644 src/hashmap.c diff --git a/.clang-format b/.clang-format index 13ffbb8..3fb3e14 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,5 @@ +BasedOnStyle: Google +ColumnLimit: 120 UseTab: Always IndentWidth: 8 TabWidth: 8 diff --git a/.vscode/settings.json b/.vscode/settings.json index b0434c4..92c3581 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { "clangd.path": "/usr/bin/clangd", - "clangd.arguments": [ "--header-insertion=never" ] + "clangd.arguments": [ "--header-insertion=never" ], } \ No newline at end of file diff --git a/README.md b/README.md index 979c488..f9eb75f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # cws -A simple Web Server written in C (learning purposes), it works only on Linux systems +A simple Web Server written in C (learning purposes), it works only on Linux systems. ## Requirements - [meson](https://mesonbuild.com/index.html) diff --git a/include/client.h b/include/client.h index 3630bbb..f404866 100644 --- a/include/client.h +++ b/include/client.h @@ -2,16 +2,16 @@ #define __CLIENT_H__ #include +#include #include #include -#include -#include - #include #include #include +#include +#include #include -int test_client_connection(const char *hostname, const char *port); +int test_client_connection(const char *hostname, const char *service); #endif diff --git a/include/hashmap.h b/include/hashmap.h new file mode 100644 index 0000000..300c4eb --- /dev/null +++ b/include/hashmap.h @@ -0,0 +1,16 @@ +#ifndef __HASHMAP_C__ +#define __HASHMAP_C__ + +#include + +#define HASHMAP_MAX_ITEMS 10000 + +struct hashmap { + int sockfd; + struct sockaddr_storage sas; +}; + +int hash(int sockfd); +void hm_insert(struct hashmap *map, int sockfd, struct sockaddr_storage *sas); + +#endif \ No newline at end of file diff --git a/include/server.h b/include/server.h index 8556a92..06dd8c3 100644 --- a/include/server.h +++ b/include/server.h @@ -2,18 +2,17 @@ #define __SERVER_H__ #include +#include +#include #include #include -#include -#include - #include #include #include -#include #include -#include -#include +#include +#include +#include #define PORT 3030 #define BACKLOG 10 @@ -24,7 +23,9 @@ int start_server(const char *hostname, const char *service); void setup_hints(struct addrinfo *hints, size_t len, const char *hostname); void handle_clients(int sockfd); void epoll_ctl_add(int epfd, int sockfd, uint32_t events); +void epoll_ctl_del(int epfd, int sockfd); void setnonblocking(int sockfd); -void handle_new_client(int sockfd); +int handle_new_client(int sockfd, struct sockaddr_storage *their_sa, socklen_t *theirsa_size); +void print_client_ip(struct sockaddr_in *sin); #endif diff --git a/include/utils.h b/include/utils.h index 62d8eaf..6251400 100644 --- a/include/utils.h +++ b/include/utils.h @@ -4,12 +4,11 @@ #include #include #include -#include -#include - #include #include #include +#include +#include // print every IPs of a hostname void print_ips(const char *hostname, const char *port); diff --git a/notes/advanced-techniques.md b/notes/advanced-techniques.md index cf009c1..e1029a1 100644 --- a/notes/advanced-techniques.md +++ b/notes/advanced-techniques.md @@ -51,7 +51,7 @@ It is similar to `poll()` but more efficient when dealing with lots of fds. The int epoll_create1(int flags); ``` -Just pass 0 for the `flags`, it is an improved version of the `epoll_create()`. It creates a new epoll instance nad returns the fd of that instance. +Just pass 0 for the `flags`, it is an improved version of the `epoll_create()`. It creates a new epoll instance and returns the fd of that instance. ```c #include diff --git a/notes/hash-table.md b/notes/hash-table.md new file mode 100644 index 0000000..70c2c64 --- /dev/null +++ b/notes/hash-table.md @@ -0,0 +1,19 @@ +# Hash Table +Well, the moment has come. I never made a Hash Map algorithm, but in this scenario I have to save both fd and sockaddr (I could make a simply linked list, but it's a way to learn new things). + +Let's start with a little bit of theory. + +A *Hash Table* is a data structure also called **dictionary** or **map**. It maps *keys* to *values* thanks to a **hash function** that computes and *index* (**hash code**) into an array of **buckets**. + +One problem could be the *hash collision* where the hash function computes the same index for different values. A fix could be the **chaining** method, where for the same hash code you can make a linked list and append the index. Then the lookup function will go through the list and find the key. + +In a Hash Map you can insert, delete and lookup (simply search). + +### Hash function +The easiest way... don't judge me. + +- Integer keys: +$$ hash(\text{key}) = \text{key} \mod \text{table\_dim} $$ + +- String keys: +$$ hash(key) = \sum_{i=0}^{len(key) - 1} ascii\_value(key[i]) * prime\_number$$ diff --git a/notes/http-request.md b/notes/http-request.md new file mode 100644 index 0000000..84d7a34 --- /dev/null +++ b/notes/http-request.md @@ -0,0 +1,11 @@ +# HTTP Request +This is an example of a basic HTTP request made from the browser: + +```bash +GET / HTTP/1.1 +User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT) +Host: www.tutorialspoint.com +Accept-Language: en-us +Accept-Encoding: gzip, deflate +Connection: Keep-Alive +``` diff --git a/src/client.c b/src/client.c index fda578c..b9e8b18 100644 --- a/src/client.c +++ b/src/client.c @@ -1,9 +1,10 @@ // THIS FILE IS ONLY A TEST FOR THE BASIC STUFF #include "client.h" + #include "colors.h" -int test_client_connection(const char *hostname, const char *port) { +int test_client_connection(const char *hostname, const char *service) { struct addrinfo hints; struct addrinfo *res; @@ -11,10 +12,9 @@ int test_client_connection(const char *hostname, const char *port) { hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_INET; - int status = getaddrinfo(hostname, port, &hints, &res); + int status = getaddrinfo(hostname, service, &hints, &res); if (status != 0) { - fprintf(stderr, RED BOLD "[client] getaddrinfo(): %s\n" RESET, - gai_strerror(status)); + fprintf(stderr, RED BOLD "[client] getaddrinfo(): %s\n" RESET, gai_strerror(status)); exit(EXIT_FAILURE); } @@ -22,15 +22,17 @@ int test_client_connection(const char *hostname, const char *port) { status = connect(sockfd, res->ai_addr, res->ai_addrlen); if (status != 0) { - fprintf(stderr, RED BOLD "[client] connect(): %s\n" RESET, - gai_strerror(status)); + fprintf(stderr, RED BOLD "[client] connect(): %s\n" RESET, strerror(errno)); exit(EXIT_FAILURE); } char buf[4096]; - int bytes_read = recv(sockfd, buf, 4096, 0); - fprintf(stdout, BLUE "[client] Read %d bytes: %s\n" RESET, bytes_read, - buf); + int bytes_read = recv(sockfd, buf, sizeof buf, 0); + fprintf(stdout, BLUE "[client] Read %d bytes: %s\n" RESET, bytes_read, buf); + + fprintf(stdout, "[client] => "); + fgets(buf, sizeof buf, stdin); + send(sockfd, buf, strlen(buf), 0); freeaddrinfo(res); close(sockfd); diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100644 index 0000000..c94c7b7 --- /dev/null +++ b/src/hashmap.c @@ -0,0 +1,9 @@ +#include "hashmap.h" + +int hash(int sockfd) { return sockfd % HASHMAP_MAX_ITEMS; } + +void hm_insert(struct hashmap *map, int sockfd, struct sockaddr_storage *sas) { + int index = hash(sockfd); + map[index].sockfd = sockfd; + map[index].sas = *sas; +} diff --git a/src/main.c b/src/main.c index bb8d6e2..ab0e21b 100644 --- a/src/main.c +++ b/src/main.c @@ -1,4 +1,5 @@ #include "main.h" + #include "colors.h" #include "server.h" diff --git a/src/mainc.c b/src/mainc.c index bc36e94..1994874 100644 --- a/src/mainc.c +++ b/src/mainc.c @@ -5,7 +5,7 @@ int main(int argc, char **argv) { fprintf(stdout, BOLD GREEN "[client] Running cws...\n" RESET); - int ret = test_client_connection("localhost", "3030"); + int ret = test_client_connection(argv[1], argv[2]); if (ret < 0) { fprintf(stderr, BOLD RED "Unable to start client\n"); } diff --git a/src/server.c b/src/server.c index 5ef14ae..687fbb2 100644 --- a/src/server.c +++ b/src/server.c @@ -1,4 +1,5 @@ #include "server.h" + #include "colors.h" int start_server(const char *hostname, const char *service) { @@ -9,25 +10,22 @@ int start_server(const char *hostname, const char *service) { int status = getaddrinfo(hostname, service, &hints, &res); if (status != 0) { - fprintf(stderr, - RED BOLD "[server] getaddrinfo() error: %s\n" RESET, - gai_strerror(status)); + fprintf(stderr, RED BOLD "[server] getaddrinfo() error: %s\n" RESET, gai_strerror(status)); exit(EXIT_FAILURE); } int sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + fprintf(stdout, YELLOW "[server] sockfd: %d\n" RESET, sockfd); status = bind(sockfd, res->ai_addr, res->ai_addrlen); if (status != 0) { - fprintf(stderr, RED BOLD "[server] bind(): %s\n" RESET, - gai_strerror(status)); + fprintf(stderr, RED BOLD "[server] bind(): %s\n" RESET, gai_strerror(status)); exit(EXIT_FAILURE); } status = listen(sockfd, BACKLOG); if (status != 0) { - fprintf(stderr, RED BOLD "[server] listen(): %s\n" RESET, - gai_strerror(status)); + fprintf(stderr, RED BOLD "[server] listen(): %s\n" RESET, gai_strerror(status)); exit(EXIT_FAILURE); } @@ -41,10 +39,10 @@ int start_server(const char *hostname, const char *service) { void setup_hints(struct addrinfo *hints, size_t len, const char *hostname) { memset(hints, 0, len); - hints->ai_family = AF_UNSPEC; // IPv4 or IPv6 - hints->ai_socktype = SOCK_STREAM; // TCP + hints->ai_family = AF_UNSPEC; // IPv4 or IPv6 + hints->ai_socktype = SOCK_STREAM; // TCP if (hostname == NULL) { - hints->ai_flags = AI_PASSIVE; // fill in IP for me + hints->ai_flags = AI_PASSIVE; // fill in IP for me } } @@ -56,55 +54,56 @@ void handle_clients(int sockfd) { setnonblocking(sockfd); epoll_ctl_add(epfd, sockfd, EPOLLIN | EPOLLET); - struct epoll_event *revents = - malloc(EPOLL_MAXEVENTS * sizeof(struct epoll_event)); + struct epoll_event *revents = malloc(EPOLL_MAXEVENTS * sizeof(struct epoll_event)); int nfds; char *msg = "Hello there!"; size_t msg_len = strlen(msg); + char data[4096]; + int client_fd; + int run = 1; - for (;;) { - nfds = - epoll_wait(epfd, revents, EPOLL_MAXEVENTS, EPOLL_TIMEOUT); + while (run) { + nfds = epoll_wait(epfd, revents, EPOLL_MAXEVENTS, EPOLL_TIMEOUT); for (int i = 0; i < nfds; ++i) { if (revents[i].data.fd == sockfd) { - int client_fd = - accept(sockfd, (struct sockaddr *)&their_sa, - &theirsa_size); + // new client + client_fd = handle_new_client(sockfd, &their_sa, &theirsa_size); + setnonblocking(client_fd); + epoll_ctl_add(epfd, client_fd, EPOLLIN); - if (client_fd == -1) { - if (errno != EWOULDBLOCK) { - fprintf(stderr, - RED BOLD "[server] " - "accept(): " - "%s\n" RESET, - strerror(errno)); - } + print_client_ip((struct sockaddr_in *)&their_sa); + + int bytes_sent = send(client_fd, msg, msg_len, 0); + fprintf(stdout, BLUE "[server] Sent %d bytes\n" RESET, bytes_sent); + } else { + // incoming data + client_fd = revents[i].data.fd; + // fprintf(stdout, "[%d] EPOLLIN\n", client_fd); + int bytes_read = recv(client_fd, data, sizeof data, 0); + + if (bytes_read == 0) { + // client disconnect + fprintf(stdout, BLUE "[server] Client disconnection\n"); + epoll_ctl_del(epfd, client_fd); + close(client_fd); continue; } - setnonblocking(client_fd); - - struct sockaddr_in *client = - (struct sockaddr_in *)&their_sa; - char client_ip[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &client->sin_addr, client_ip, - INET_ADDRSTRLEN); - fprintf(stdout, - BLUE "[server] Incoming " - "client, ip: %s\n" RESET, - client_ip); - - int bytes_sent = - send(client_fd, msg, msg_len, 0); - fprintf(stdout, - BLUE "[server] Sent %d bytes\n" RESET, - bytes_sent); - close(client_fd); + data[strlen(data) - 1] = '\0'; + fprintf(stdout, "[server] Bytes read (%d): %s\n", bytes_read, data); + if (strcmp(data, "stop") == 0) { + fprintf(stdout, GREEN BOLD "[server] Stopping...\n" RESET); + run = 0; + break; + } } } } + + free(revents); + close(epfd); } void epoll_ctl_add(int epfd, int sockfd, uint32_t events) { @@ -113,8 +112,15 @@ void epoll_ctl_add(int epfd, int sockfd, uint32_t events) { event.data.fd = sockfd; int status = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event); if (status != 0) { - fprintf(stderr, RED BOLD "[server] epoll_ctl(): %s\n" RESET, - gai_strerror(status)); + fprintf(stderr, RED BOLD "[server] epoll_ctl_add(): %s\n" RESET, strerror(errno)); + exit(EXIT_FAILURE); + } +} + +void epoll_ctl_del(int epfd, int sockfd) { + int status = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL); + if (status != 0) { + fprintf(stdout, RED BOLD "[server] epoll_ctl_del(): %s\n" RESET, strerror(errno)); exit(EXIT_FAILURE); } } @@ -122,12 +128,35 @@ void epoll_ctl_add(int epfd, int sockfd, uint32_t events) { void setnonblocking(int sockfd) { int status = fcntl(sockfd, F_SETFL, O_NONBLOCK); if (status == -1) { - fprintf(stderr, RED BOLD "[server] fcntl(): %s\n" RESET, - gai_strerror(status)); + fprintf(stderr, RED BOLD "[server] fcntl(): %s\n" RESET, gai_strerror(status)); exit(EXIT_FAILURE); } } -void handle_new_client(int sockfd) { - // handle here new clients +int handle_new_client(int sockfd, struct sockaddr_storage *their_sa, socklen_t *theirsa_size) { + int client_fd = accept(sockfd, (struct sockaddr *)their_sa, theirsa_size); + + if (client_fd == -1) { + if (errno != EWOULDBLOCK) { + fprintf(stderr, + RED BOLD + "[server] " + "accept(): " + "%s\n" RESET, + strerror(errno)); + } + return -1; + } + + return client_fd; } + +void print_client_ip(struct sockaddr_in *sin) { + char ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &sin->sin_addr, ip, INET_ADDRSTRLEN); + fprintf(stdout, + BLUE + "[server] Incoming " + "client, IP: %s\n" RESET, + ip); +} \ No newline at end of file diff --git a/src/utils.c b/src/utils.c index 937cd0b..7f6b9e8 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,4 +1,5 @@ #include "utils.h" + #include "colors.h" void print_ips(const char *hostname, const char *port) { @@ -12,8 +13,7 @@ void print_ips(const char *hostname, const char *port) { int status = getaddrinfo(hostname, port, &ai, &res); if (status < 0) { - fprintf(stderr, RED "getaddrinfo(): %s\n" RESET, - gai_strerror(status)); + fprintf(stderr, RED "getaddrinfo(): %s\n" RESET, gai_strerror(status)); exit(1); } @@ -22,16 +22,12 @@ void print_ips(const char *hostname, const char *port) { for (struct addrinfo *p = res; p != NULL; p = p->ai_next) { if (p->ai_family == AF_INET) { - struct sockaddr_in *sin = - (struct sockaddr_in *)p->ai_addr; - inet_ntop(AF_INET, &sin->sin_addr, ipv4, - INET_ADDRSTRLEN); + struct sockaddr_in *sin = (struct sockaddr_in *)p->ai_addr; + inet_ntop(AF_INET, &sin->sin_addr, ipv4, INET_ADDRSTRLEN); fprintf(stdout, BLUE "%s\n" RESET, ipv4); } else if (p->ai_family == AF_INET6) { - struct sockaddr_in6 *sin6 = - (struct sockaddr_in6 *)p->ai_addr; - inet_ntop(AF_INET6, &sin6->sin6_addr, ipv6, - INET6_ADDRSTRLEN); + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)p->ai_addr; + inet_ntop(AF_INET6, &sin6->sin6_addr, ipv6, INET6_ADDRSTRLEN); fprintf(stdout, BLUE "%s\n" RESET, ipv6); } }