206 lines
4.6 KiB
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);
|
|
}
|
|
}
|
|
}
|