commit 46d0d5cce06ba2b14a3a94d961215eb1af596244 Author: ari melody Date: Mon Apr 27 21:47:48 2026 +0100 bunny -> 🐇 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4077f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +server +vgcore.* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cfa9068 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +.PHONY: all + +all: server + +server: + $(CC) -O2 -g -o server src/main.c + +clean: + rm -rf server diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..2e44525 --- /dev/null +++ b/src/main.c @@ -0,0 +1,243 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CLIENTS 1024 +#define BUFFER_SIZE 1024 +#define WORKER_THREADS 8 +#define DEFAULT_HOST "0.0.0.0" + +#define HTTP_RESPONSE \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/plain; charset=utf-8\r\n" \ + "Content-Length: 14\r\n" \ + "\r\n" \ + "bunny -> 🐇\n" \ + +struct app_state { + struct sockaddr_in addr; + int sock; + int epoll; + pthread_mutex_t epoll_lock; + char *page_buffer; +}; + +struct client { + char active; + int sock; + char *addr_str; + pthread_mutex_t lock; +}; +struct client clients[MAX_CLIENTS]; + +int accept_connection(struct app_state *app) { + struct sockaddr_in addr; + socklen_t addrlen = sizeof(struct sockaddr_in); + + struct epoll_event ev; + + int sock = accept(app->sock, (struct sockaddr*)&addr, &addrlen); + if (sock < 0) { + perror("Failed to accept connection"); + return -1; + } + + // add client to epoll instance + ev.events = EPOLLIN; + ev.data.fd = sock; + pthread_mutex_lock(&app->epoll_lock); + if (epoll_ctl(app->epoll, EPOLL_CTL_ADD, sock, &ev) == -1) { + perror("Failed to add client to epoll"); + pthread_mutex_unlock(&app->epoll_lock); + return -1; + } + pthread_mutex_unlock(&app->epoll_lock); + + struct client *client = &clients[sock % MAX_CLIENTS]; + + pthread_mutex_lock(&client->lock); + + memset(client, 0, sizeof(struct client)); + client->active = 1; + client->sock = sock; + + char *addr_str = inet_ntoa(addr.sin_addr); + size_t addr_strlen = snprintf(NULL, 0, "%s:%d", addr_str, addr.sin_port) + 1; + client->addr_str = malloc(addr_strlen * sizeof(char)); + snprintf(client->addr_str, addr_strlen, "%s:%d", addr_str, addr.sin_port); + + pthread_mutex_unlock(&client->lock); + + printf("Connected: %s\n", client->addr_str); + + return 0; +} + +void close_client(struct app_state *app, struct client *client, int read) { + if (read == -1) { + fprintf(stderr, "Failed to read [%s]: %s\n", + client->addr_str, strerror(errno)); + } + + printf("Disonnected: %s\n", client->addr_str); + + pthread_mutex_lock(&app->epoll_lock); + if (epoll_ctl(app->epoll, EPOLL_CTL_DEL, client->sock, NULL) == -1) + fprintf(stderr, "Failed to remove [%s] from epoll: %s\n", + client->addr_str, strerror(errno)); + pthread_mutex_unlock(&app->epoll_lock); + + if (shutdown(client->sock, SHUT_RDWR) == -1) + fprintf(stderr, "Failed to shutdown socket [%s]: %s\n", + client->addr_str, strerror(errno)); + + if (close(client->sock) == -1) + fprintf(stderr, "Failed to close socket [%s]: %s\n", + client->addr_str, strerror(errno)); + + client->active = 0; +} + +int str_has_suffix(char *str, const char *suffix) { + if (str == NULL || suffix == NULL) + return 0; + + size_t lenstr = strlen(str); + size_t lensuffix = strlen(suffix); + if (lensuffix > lenstr) + return 0; + + return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0; +} + +void handle_client(struct app_state *app, struct client *client) { + pthread_mutex_lock(&client->lock); + if (!client->active) { + pthread_mutex_unlock(&client->lock); + return; + } + + char buffer[BUFFER_SIZE + 1]; // ensure space for \0 + memset(buffer, 0, BUFFER_SIZE); + + int read = recv(client->sock, buffer, BUFFER_SIZE, 0); + if (read > 0) { + if (str_has_suffix(buffer, "\r\n\r\n")) { + if (send(client->sock, HTTP_RESPONSE, strlen(HTTP_RESPONSE), 0) == -1) + fprintf(stderr, "Failed to send [%s]: %s\n", + client->addr_str, strerror(errno)); + close_client(app, client, 0); + } + } else { + close_client(app, client, read); + } + + pthread_mutex_unlock(&client->lock); + + return; +} + +int main(int argc, char *argv[]) { + struct app_state app; + memset(&app, 0, sizeof(struct app_state)); + + if (argc < 2) { + printf("usage: server "); + return 0; + } + + uint16_t port = atoi(argv[1]); + + app.sock = socket(AF_INET, SOCK_STREAM, 0); + if (app.sock == -1) { + perror("Failed to create socket"); + return 1; + } + + memset(&app.addr, 0, sizeof(struct sockaddr_in)); + app.addr.sin_family = AF_INET; + app.addr.sin_port = htons(port); + inet_aton(DEFAULT_HOST, &app.addr.sin_addr); + + if (bind(app.sock, (struct sockaddr*)&app.addr, sizeof(struct sockaddr_in)) == -1) { + close(app.sock); + perror("Failed to bind to port"); + return 1; + } + + if (listen(app.sock, MAX_CLIENTS) == -1) { + close(app.sock); + perror("Failed to bind to port"); + return 1; + } + + app.epoll = epoll_create(MAX_CLIENTS); + if (app.epoll < 0) { + close(app.epoll); + close(app.sock); + perror("Failed to create epoll instance"); + return 1; + } + pthread_mutex_init(&app.epoll_lock, NULL); + + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = app.sock; + if (epoll_ctl(app.epoll, EPOLL_CTL_ADD, app.sock, &ev) == -1) { + close(app.epoll); + close(app.sock); + perror("Failed to create epoll hook for listen socket"); + return 1; + } + + int n_events = 0; + struct epoll_event events[MAX_CLIENTS]; + memset(events, 0, sizeof(struct epoll_event) * MAX_CLIENTS); + int event_i = 0; + + memset(clients, 0, sizeof(struct client) * MAX_CLIENTS); + for (size_t i = 0; i < MAX_CLIENTS; i++) + pthread_mutex_init(&clients[i].lock, NULL); + + app.page_buffer = malloc(1024 * sizeof(struct app_state)); + + printf("Now listening on %s:%d\n", DEFAULT_HOST, port); + + char running = 1; + do { + n_events = epoll_wait(app.epoll, events, MAX_CLIENTS, -1); + if (n_events == -1) { + perror("Failed to poll events"); + running = 0; + continue; + } + + for (event_i = 0; event_i < n_events; event_i++) { + struct epoll_event *event = &events[event_i]; + + if (event->data.fd == app.sock) { + // client is waiting to be accepted on the listen socket + accept_connection(&app); + } else { + handle_client(&app, &clients[event->data.fd % MAX_CLIENTS]); + } + } + } while (running == 1); + + printf("Shutting down...\n"); + + if (close(app.epoll) == -1) + perror("Failed to close epoll"); + if (close(app.sock) == -1) + perror("Failed to close socket"); + + return 0; +}