#include #include #include #include #include #include #include #include #include #include #include #include "event_loop.h" #include "log.h" #include "picohttpparser.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 *loop, 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 = calloc(sizeof(event_loop_conn), 1); // 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(loop, conn); if (res != 0) { close(connfd); return -4; } 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, 1000); 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(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); free(conn); } } } // try to accept a new connection if the listening fd is active if (poll_args[0].revents) { event_loop_accept(el, fd); } } }