Skip to content

Instantly share code, notes, and snippets.

@cpq
Created May 1, 2024 09:16
Show Gist options
  • Save cpq/bc8a64edb9aca46e94aca1fc5d743a6b to your computer and use it in GitHub Desktop.
Save cpq/bc8a64edb9aca46e94aca1fc5d743a6b to your computer and use it in GitHub Desktop.
Final code for the "BSD socket API explained" video
// 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