Skip to content

Instantly share code, notes, and snippets.

@iemelyanov
Last active November 5, 2022 15:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iemelyanov/4ae84bf8b857f81169345e00d5bcd204 to your computer and use it in GitHub Desktop.
Save iemelyanov/4ae84bf8b857f81169345e00d5bcd204 to your computer and use it in GitHub Desktop.
aiosrv.c
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <search.h>
#define HOST "127.0.0.1"
#define PORT 3000
#define BUF_CAP 4096
#define MAX_EVENTS 128
typedef enum errKind {
ERR_OK,
ERR_FCNTLSETFLAG,
ERR_FCNTLSETNONBLOCK,
ERR_EPOLLCTLADD,
ERR_EPOLLCTLDEL,
ERR_EPOLLCTLMOD,
ERR_EPOLLCREATE,
ERR_EPOLLWAIT,
ERR_UNKNOWN_EVENT_FD,
ERR_ACCCEPT,
ERR_CLOSE,
ERR_READ,
ERR_SEND,
} errKind;
typedef struct evLoop evLoop;
typedef errKind (*cbFn)(evLoop *el, int event_fd);
typedef struct evData {
int fd;
cbFn cb;
} evData;
typedef struct evMap {
void *root;
} evMap;
int evDataCompare(const void *pa, const void *pb) {
const evData *a = pa, *b = pb;
return a->fd - b->fd;
}
evData *evDataNew(int fd, cbFn cb) {
evData *d = malloc(sizeof(*d));
d->fd = fd;
d->cb = cb;
return d;
}
int evMapSet(evMap *m, int fd, cbFn cb) {
evData d = {.fd = fd, .cb = cb };
void **node = tsearch(&d, &m->root, evDataCompare);
if (!node) return -1;
evData *t = *node;
if (t == &d) {
t = evDataNew(fd, cb);
*node = t;
}
t->cb = cb;
return 0;
}
evData *evMapGet(const evMap *m, int fd) {
evData d = {.fd = fd };
void **node = tfind(&d, &m->root, evDataCompare);
if (!node) return NULL;
evData *t = *node;
return t;
}
void evMapDel(evMap *m, int fd) {
evData *d = evMapGet(m, fd);
if (!d) return;
tdelete(d, &m->root, evDataCompare);
free(d);
}
struct evLoop {
evMap map;
int epoll_fd;
int socket_fd;
};
errKind evLoopRecive(evLoop *el, int event_fd);
errKind evLoopSend(evLoop *el, int event_fd);
errKind evLoopAccept(evLoop *el, int socket_fd) {
int conn_socket_fd = accept(socket_fd, NULL, NULL);
if (conn_socket_fd < 0) {
return ERR_ACCCEPT;
}
int flags = fcntl(conn_socket_fd, F_GETFL, 0);
if (flags == -1) {
return ERR_FCNTLSETFLAG;
}
if (fcntl(conn_socket_fd, F_SETFL, flags | O_NONBLOCK) == -1) {
return ERR_FCNTLSETNONBLOCK;
}
struct epoll_event ev = {
.events = EPOLLIN, // | EPOLLET,
.data.fd = conn_socket_fd,
};
if (epoll_ctl(el->epoll_fd, EPOLL_CTL_ADD, conn_socket_fd, &ev)) {
return ERR_EPOLLCTLADD;
}
if (evMapSet(&el->map, conn_socket_fd, evLoopRecive) < 0) {
return ERR_ACCCEPT;
}
return ERR_OK;
}
errKind evLoopRecive(evLoop *el, int event_fd) {
ssize_t nread;
char buf[BUF_CAP] = { 0 };
int buf_size = 0;
while ((nread = recv(event_fd, buf + buf_size, BUF_CAP - buf_size, 0)) > 0) {
buf_size += nread;
}
// Client drop connection
if (nread == 0) {
struct epoll_event ev = {
.events = 0,
.data.fd = event_fd,
};
if (epoll_ctl(el->epoll_fd, EPOLL_CTL_DEL, event_fd, &ev)) {
return ERR_EPOLLCTLDEL;
}
evMapDel(&el->map, event_fd);
if (close(event_fd)) {
return ERR_CLOSE;
}
return ERR_OK;
}
if (errno == ECONNRESET) {
if (close(event_fd)) {
return ERR_CLOSE;
}
evMapDel(&el->map, event_fd);
return ERR_OK;
}
if (errno != EAGAIN && errno != EWOULDBLOCK) {
if (close(event_fd)) {
return ERR_CLOSE;
}
return ERR_READ;
}
struct epoll_event ev = {
.events = EPOLLOUT, // | EPOLLET,
.data.fd = event_fd,
};
if (epoll_ctl(el->epoll_fd, EPOLL_CTL_MOD, event_fd, &ev)) {
return ERR_EPOLLCTLMOD;
}
evMapSet(&el->map, event_fd, evLoopSend);
return ERR_OK;
}
errKind evLoopSend(evLoop *el, int event_fd) {
const char *resp =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Content-Length: 13\r\n\r\n"
"Hello World\r\n";
if (send(event_fd, resp, strlen(resp), 0) < 0) {
return ERR_SEND;
}
struct epoll_event ev = {
.events = EPOLLIN, // | EPOLLET,
.data.fd = event_fd,
};
if (epoll_ctl(el->epoll_fd, EPOLL_CTL_MOD, event_fd, &ev) == -1) {
return ERR_EPOLLCTLMOD;
}
evMapSet(&el->map, event_fd, evLoopRecive);
return ERR_OK;
}
errKind evLoopRun(evLoop *el) {
el->epoll_fd = epoll_create1(0);
if (el->epoll_fd == -1) {
return ERR_EPOLLCREATE;
}
struct epoll_event ev = {
.events = EPOLLIN,
.data.fd = el->socket_fd,
};
if (epoll_ctl(el->epoll_fd, EPOLL_CTL_ADD, el->socket_fd, &ev) == -1) {
return ERR_EPOLLCTLADD;
}
struct epoll_event events[MAX_EVENTS];
while (true) {
int num_fds = epoll_wait(el->epoll_fd, events, MAX_EVENTS, -1);
if (num_fds == -1) {
return ERR_EPOLLWAIT;
}
for (int i = 0; i < num_fds; ++i) {
if (events[i].data.fd == el->socket_fd) {
errKind err = evLoopAccept(el, el->socket_fd);
if (err != ERR_OK) {
return err;
}
} else {
evData *d = evMapGet(&el->map, events[i].data.fd);
if (!d) {
return ERR_UNKNOWN_EVENT_FD;
} else {
errKind err = d->cb(el, events[i].data.fd);
if (err != ERR_OK) {
return err;
}
}
}
}
}
return ERR_OK;
}
int newTCPListener(const char *host, int port) {
int socket_fd;
if ((socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)) == -1) {
return -1;
}
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int))) {
return -1;
}
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr = {
.s_addr = inet_addr(host),
},
.sin_zero = {},
};
if (bind(socket_fd, (const struct sockaddr*)(&addr), sizeof(addr))) {
return -1;
}
if (listen(socket_fd, SOMAXCONN) == -1) {
return -1;
}
return socket_fd;
}
int main(int argc, char **argv) {
int socket_fd = newTCPListener(HOST, PORT);
evLoop loop = {
.map = {
.root = NULL
},
.epoll_fd = 0,
.socket_fd = socket_fd,
};
if (evLoopRun(&loop) != ERR_OK) {
perror("event loop");
exit(EXIT_FAILURE);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment