-
-
Save indutny/2bbef0db7b225d3e9e5c to your computer and use it in GitHub Desktop.
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 <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <assert.h> | |
#include <unistd.h> | |
#include <signal.h> | |
#include <errno.h> | |
#include <sys/ioctl.h> | |
/* kqueue */ | |
#include <sys/types.h> | |
#include <sys/event.h> | |
#include <sys/time.h> | |
/* sockets */ | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#define EVENT_COUNT 1024 | |
typedef struct client_s client_t; | |
struct client_s { | |
int fd; | |
char buff[10 * 1024]; | |
int headers; | |
int sent; | |
}; | |
int create_server(int port) { | |
int r; | |
int fd; | |
int enable; | |
sockaddr_in addr; | |
fd = socket(AF_INET, SOCK_STREAM, 0); | |
assert(fd >= 0); | |
enable = 1; | |
r = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)); | |
assert(r == 0); | |
memset(&addr, 0, sizeof(addr)); | |
addr.sin_family = AF_INET; | |
addr.sin_port = htons(port); | |
r = bind(fd, (sockaddr*) &addr, sizeof(addr)); | |
assert(r == 0); | |
r = listen(fd, 1024); | |
assert(r == 0); | |
return fd; | |
} | |
void accept_incoming(int kq, int server) { | |
sockaddr_in addr; | |
socklen_t addr_size; | |
struct kevent events[2]; | |
client_t* client; | |
int r; | |
int fd; | |
int enable; | |
addr_size = sizeof(addr); | |
fd = accept(server, (sockaddr*) &addr, &addr_size); | |
assert(fd >= 0); | |
client = (client_t*) malloc(sizeof(client_t)); | |
assert(client != NULL); | |
memset(client, 0, sizeof(*client)); | |
client->fd = fd; | |
/* Make socket non-blocking */ | |
enable = 1; | |
do { | |
r = ioctl(fd, FIONBIO, &enable); | |
} while (r == -1 && errno == EINTR); | |
/* Watch new connection */ | |
EV_SET(&events[0], fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, client); | |
EV_SET(&events[1], fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, client); | |
r = kevent(kq, events, 2, NULL, 0, NULL); | |
assert(r == 0); | |
} | |
void disconnect_client(int kq, client_t* client) { | |
struct kevent events[2]; | |
int r; | |
/* Remove event from kqueue */ | |
EV_SET(&events[0], | |
client->fd, | |
EVFILT_READ, | |
EV_DELETE | EV_DISABLE, | |
0, | |
0, | |
client); | |
EV_SET(&events[1], | |
client->fd, | |
EVFILT_WRITE, | |
EV_DELETE | EV_DISABLE, | |
0, | |
0, | |
client); | |
r = kevent(kq, events, 2, NULL, 0, NULL); | |
assert(r == 0); | |
/* Close socket */ | |
close(client->fd); | |
/* Free memory */ | |
free(client); | |
} | |
void handle_read(int kq, client_t* client, int bytes) { | |
char buff[1024]; | |
size_t r; | |
r = read(client->fd, buff, bytes); | |
assert(r >= 0); | |
} | |
void handle_write(int kq, client_t* client, int bytes) { | |
if (client->sent) return; | |
client->sent = 1; | |
const char* res = "HTTP/1.1 200 Ok\r\n\r\nhello"; | |
write(client->fd, res, strlen(res)); | |
shutdown(client->fd, SHUT_WR); | |
} | |
int main() { | |
int r; | |
int kq; | |
int server; | |
int numevents; | |
int i; | |
int recv_signal; | |
struct kevent events[EVENT_COUNT]; | |
struct sigaction sigact; | |
/* Ignore SIGPIPE */ | |
memset(&sigact, 0, sizeof(sigact)); | |
sigact.sa_handler = SIG_IGN; | |
sigaction(SIGPIPE, &sigact, 0); | |
memset(&sigact, 0, sizeof(sigact)); | |
sigact.sa_handler = SIG_IGN; | |
sigaction(SIGINT, &sigact, 0); | |
kq = kqueue(); | |
assert(kq >= 0); | |
server = create_server(8080); | |
fprintf(stdout, "Server fd: %d\n", server); | |
/* Watch incoming connections */ | |
EV_SET(&events[0], server, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0); | |
/* Watch signals */ | |
EV_SET(&events[1], SIGINT, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, 0); | |
r = kevent(kq, events, 2, NULL, 0, NULL); | |
assert(r == 0); | |
recv_signal = 0; | |
while (!recv_signal) { | |
numevents = kevent(kq, NULL, 0, events, EVENT_COUNT, NULL); | |
if (numevents < 0) { | |
if (errno == EINTR) continue; | |
fprintf(stderr, "Error: %d\n", errno); | |
abort(); | |
} | |
for (i = 0; i < numevents; i++) { | |
/* Skip errors for now */ | |
if (events[i].flags & EV_ERROR) continue; | |
/* Handle signals */ | |
if (events[i].filter == EVFILT_SIGNAL) { | |
recv_signal = 1; | |
break; | |
} | |
/* Handle incoming connections */ | |
if (events[i].filter == EVFILT_READ && events[i].ident == server) { | |
while (events[i].data--) { | |
accept_incoming(kq, server); | |
} | |
} | |
/* XXX: Why is it happening? */ | |
if (events[i].udata == NULL) continue; | |
if (events[i].filter == EVFILT_READ) { | |
if (events[i].flags & EV_EOF) { | |
int j; | |
disconnect_client(kq, (client_t*) events[i].udata); | |
/* Ignore events for closed fd */ | |
for (j = i + 1; j < numevents; j++) { | |
if (events[i].ident == events[j].ident) events[j].udata = NULL; | |
} | |
continue; | |
} | |
handle_read(kq, (client_t*) events[i].udata, events[i].data); | |
} else if (events[i].filter == EVFILT_WRITE) { | |
assert(events[i].udata != NULL); | |
handle_write(kq, (client_t*) events[i].udata, events[i].data); | |
} else { | |
fprintf(stderr, "Unexpected filter: %d\n", events[i].filter); | |
abort(); | |
} | |
} | |
} | |
fprintf(stdout, "\nClosing stuff\n"); | |
close(server); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment