Last active
August 28, 2024 12:24
-
-
Save cpq/bc8a64edb9aca46e94aca1fc5d743a6b to your computer and use it in GitHub Desktop.
Final code for the "BSD socket API explained" video
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Source for the https://www.youtube.com/watch?v=pp9AM5A1mDs | |
// Written & tested on MacOS | |
// To discuss or report issues, join https://discord.gg/KfR8E6wSds | |
#include <arpa/inet.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <netinet/in.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/socket.h> | |
#include <time.h> | |
#include <unistd.h> | |
#include <string.h> | |
enum { EV_OPEN, EV_CLOSE, EV_READ, EV_HTTP_REQUEST }; | |
struct conn { | |
struct conn *next; // Next connection in the linked list | |
int fd; // Socket (file descriptor) | |
char buf[512]; // HTTP Request data | |
int len; // Number of bytes read so far | |
int is_listening; // Connection is a listening connection | |
int is_closing; // Connection must be closed | |
int is_readable; // Connection is readable | |
void (*fn)(struct conn *, int event); | |
}; | |
struct server { | |
struct conn *conns; // List of all active connections | |
}; | |
void add_connection(struct server *server, int fd, int is_listening, | |
void (*fn)(struct conn *, int)) { | |
struct conn *c = calloc(1, sizeof(*c)); | |
c->next = server->conns; | |
server->conns = c; | |
c->fd = fd; | |
c->is_listening = is_listening; | |
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); | |
c->fn = fn; | |
fn(c, EV_OPEN); | |
} | |
void server_poll(struct server *server) { | |
fd_set rset; | |
FD_ZERO(&rset); | |
int max = 0; | |
// First iteration: add all sockets to the read set | |
for (struct conn *c = server->conns; c != NULL; c = c->next) { | |
FD_SET(c->fd, &rset); | |
c->is_readable = 0; | |
if (c->fd > max) max = c->fd; | |
} | |
// Tell kernel: wait until any of the sockets is readable | |
if (select(max + 1, &rset, NULL, NULL, NULL) < 0) return; | |
// Now, some of the sockets are readable, mark those | |
for (struct conn *c = server->conns; c != NULL; c = c->next) { | |
c->is_readable = FD_ISSET(c->fd, &rset); | |
} | |
// Iterate over all connection, accept/read/write | |
for (struct conn *tmp, *c = server->conns; c != NULL; c = tmp) { | |
tmp = c->next; | |
if (c->is_readable == 0) continue; // Not readable, skip it | |
if (c->is_listening) { | |
// Listening connection: accept new connection | |
struct sockaddr_in sin2; | |
socklen_t slen = sizeof(sin2); | |
int sock = accept(c->fd, (struct sockaddr *) &sin2, &slen); | |
if (sock < 0) continue; | |
add_connection(server, sock, 0, c->fn); | |
printf("Accepted new connection from %s:%hu\n", inet_ntoa(sin2.sin_addr), | |
ntohs(sin2.sin_port)); | |
} else { | |
// Accepted connection: read requests, write responses | |
int n = read(c->fd, c->buf + c->len, sizeof(c->buf) - c->len); | |
if (n < 0 && errno == EAGAIN) { | |
// No data yet, do nothing | |
} else if (n <= 0) { | |
c->is_closing = 1; | |
} else { | |
printf("Read %d bytes from %d\n", n, c->fd); | |
c->len += n; | |
c->fn(c, EV_READ); | |
// Did we get an empty line? | |
if (memmem(c->buf, c->len, "\r\n\r\n", 4) != NULL || | |
memmem(c->buf, c->len, "\n\n", 2) != NULL) { | |
// Yes! Send a response | |
c->fn(c, EV_HTTP_REQUEST); | |
c->is_closing = 1; | |
} | |
} | |
} | |
if (c->is_closing) { | |
// Remove from the linked list | |
struct conn **head = &server->conns; | |
while (*head != c) head = &(*head)->next; | |
*head = c->next; | |
printf("Closing %p %d\n", c, c->fd); | |
c->fn(c, EV_CLOSE); | |
close(c->fd); | |
free(c); | |
} | |
} | |
} | |
static void ev_handler(struct conn *c, int event) { | |
if (event == EV_OPEN) { | |
printf("Connection %p opened, fd %d\n", c, c->fd); | |
} | |
if (event == EV_CLOSE) { | |
printf("Connection %p closed, fd %d\n", c, c->fd); | |
} | |
if (event == EV_READ) { | |
printf("Connection %p read data, fd %d, len %d\n", c, c->fd, c->len); | |
} | |
if (event == EV_HTTP_REQUEST) { | |
printf("Connection %p HTTP request!, fd %d, len %d\n", c, c->fd, c->len); | |
char resp[128]; | |
time_t now = time(NULL); | |
printf("Sending response\n"); | |
snprintf(resp, sizeof(resp), | |
"HTTP/1.0 200 OK\n" | |
"Content-Length: 25\n\n%s", | |
ctime(&now)); | |
write(c->fd, resp, strlen(resp)); | |
} | |
} | |
int main(void) { | |
uint16_t port = 9000; | |
struct sockaddr_in sin = {.sin_port = htons(port), .sin_family = AF_INET}; | |
int on = 1, lsn; | |
lsn = socket(PF_INET, SOCK_STREAM, 0); | |
setsockopt(lsn, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); | |
bind(lsn, (struct sockaddr *) &sin, sizeof(sin)); | |
listen(lsn, 128); | |
struct server server = {.conns = NULL}; | |
add_connection(&server, lsn, 1, ev_handler); | |
printf("Listening on port %hu\n", port); | |
// Infinite loop accepting new connections | |
for (;;) { | |
server_poll(&server); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment