Created
May 1, 2024 09:16
-
-
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 | |
#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> | |
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 *c = server->conns; c != NULL; c = 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; | |
} | |
} | |
} | |
} | |
// Iterate over all connections, close those which are marked | |
struct conn **head = &server->conns; | |
while (*head != NULL) { | |
struct conn *c = *head, **next = &(*head)->next; | |
if (c->is_closing) { | |
printf("Closing %d\n", c->fd); | |
*head = c->next; | |
c->fn(c, EV_CLOSE); | |
close(c->fd); | |
free(c); | |
} | |
head = next; | |
} | |
} | |
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