bunny -> 🐇
This commit is contained in:
commit
46d0d5cce0
3 changed files with 254 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
server
|
||||
vgcore.*
|
||||
9
Makefile
Normal file
9
Makefile
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.PHONY: all
|
||||
|
||||
all: server
|
||||
|
||||
server:
|
||||
$(CC) -O2 -g -o server src/main.c
|
||||
|
||||
clean:
|
||||
rm -rf server
|
||||
243
src/main.c
Normal file
243
src/main.c
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#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 <port>");
|
||||
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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue