lander/src/event_loop/event_loop.c

206 lines
4.6 KiB
C

#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "event_loop.h"
#include "log.h"
static int event_loop_fd_set_nb(int fd) {
int flags = fcntl(fd, F_GETFL);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
return 0;
}
event_loop *event_loop_init() {
event_loop *el = calloc(sizeof(event_loop), 1);
// No idea if this is a good starter value
el->connections = calloc(sizeof(event_loop_conn), 16);
el->connection_count = 16;
return el;
}
int event_loop_put(event_loop *el, event_loop_conn *conn) {
if ((size_t)conn->fd >= el->connection_count) {
event_loop_conn **resized =
realloc(el->connections, sizeof(event_loop_conn) * (conn->fd + 1));
if (resized == NULL) {
return -1;
}
el->connections = resized;
el->connection_count = conn->fd + 1;
}
el->connections[conn->fd] = conn;
return 0;
}
int event_loop_accept(event_loop *el, int fd) {
struct sockaddr_in client_addr;
socklen_t socklen = sizeof(client_addr);
int connfd = accept(fd, (struct sockaddr *)&client_addr, &socklen);
if (connfd < 0) {
return -1;
}
// set the new connection fd to nonblocking mode
int res = event_loop_fd_set_nb(connfd);
if (res < 0) {
close(connfd);
return -2;
}
// creating the struct Conn
event_loop_conn *conn = event_loop_conn_init(el);
// Close the connectoin if we fail to allocate a connection struct
if (conn == NULL) {
close(connfd);
return -3;
}
conn->fd = connfd;
conn->state = event_loop_conn_state_req;
res = event_loop_put(el, conn);
if (res != 0) {
close(connfd);
return -4;
}
debug("Connection established on fd %i", connfd);
return 0;
}
void event_loop_run(event_loop *el, int port) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
critical(1, "Failed to open listening socket, errno: %i", errno);
}
int val = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
// bind
struct sockaddr_in addr = {.sin_family = AF_INET,
.sin_port = ntohs(port),
.sin_addr.s_addr = ntohl(0)};
int res = bind(fd, (const struct sockaddr *)&addr, sizeof(addr));
if (res) {
critical(1, "Failed to bind listening socket, errno: %i", errno);
}
debug("Listening socket bound to fd %i", fd);
res = listen(fd, SOMAXCONN);
if (res) {
critical(1, "Failed to start listening on listening socket, errno: %i",
errno);
}
// The listening socket is always poll'ed in non-blocking mode as well
res = event_loop_fd_set_nb(fd);
if (res != 0) {
critical(1, "Failed to set listening socket to non-blocking, errno: %i",
errno);
}
// TODO don't hardcode the number 32
struct pollfd *poll_args = calloc(sizeof(struct pollfd), 32);
size_t poll_args_count;
// for convenience, the listening fd is put in the first position
struct pollfd pfd = {fd, POLLIN, 0};
poll_args[0] = pfd;
event_loop_conn *conn;
int events;
info("Starting event loop on port %i", port);
while (1) {
poll_args_count = 1;
// connection fds
for (size_t i = 0; i < el->connection_count; i++) {
conn = el->connections[i];
if (conn == NULL) {
continue;
}
events = (conn->state == event_loop_conn_state_req) ? POLLIN : POLLOUT;
events |= POLLERR;
struct pollfd pfd = {conn->fd, events, 0};
poll_args[poll_args_count] = pfd;
poll_args_count++;
// We do at most 32 connections at a time for now
if (poll_args_count == 32)
break;
}
// poll for active fds
// the timeout argument doesn't matter here
int rv = poll(poll_args, (nfds_t)poll_args_count, 0);
if (rv < 0) {
critical(1, "Poll failed, errno: %i", errno);
}
// process active connections
for (size_t i = 1; i < poll_args_count; i++) {
if (poll_args[i].revents) {
conn = el->connections[poll_args[i].fd];
event_loop_conn_io(el, conn);
if (conn->state == event_loop_conn_state_end) {
// client closed normally, or something bad happened.
// destroy this connection
el->connections[conn->fd] = NULL;
close(conn->fd);
debug("Connection closed on fd %i", conn->fd);
event_loop_conn_free(el, conn);
}
}
}
// try to accept a new connection if the listening fd is active
if (poll_args[0].revents) {
event_loop_accept(el, fd);
}
}
}