Last active
November 5, 2022 15:09
-
-
Save iemelyanov/4ae84bf8b857f81169345e00d5bcd204 to your computer and use it in GitHub Desktop.
aiosrv.c
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
#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