From af4127bfb7a84861039025b8721f0bdff43133a8 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Tue, 13 May 2025 00:59:24 +0200 Subject: [PATCH] Add example using the HTTP engine with I/O completion ports --- Makefile | 5 +- examples/iocp_server_with_engine.c | 226 +++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 examples/iocp_server_with_engine.c diff --git a/Makefile b/Makefile index 18e9715..570c28e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: all report -OSTAG = LINUX -#OSTAG = WINDOWS +#OSTAG = LINUX +OSTAG = WINDOWS EXT_WINDOWS = .exe EXT_LINUX = .out @@ -15,6 +15,7 @@ EXT = ${EXT_${OSTAG}} all: gcc -o simple_server$(EXT) examples/simple_server.c tinyhttp.c -Wall -Wextra -DHTTP_CLIENT=0 -DHTTP_ROUTER=0 $(LFLAGS) gcc -o blocking_server_with_engine$(EXT) examples/blocking_server_with_engine.c tinyhttp.c -Wall -Wextra -DHTTP_CLIENT=0 -DHTTP_ROUTER=0 $(LFLAGS) -g3 + gcc -o iocp_server_with_engine.exe examples/iocp_server_with_engine.c tinyhttp.c -Wall -Wextra -lws2_32 gcc -o test$(EXT) tests/test.c tests/test_branch_coverage_parse.c tests/test_branch_coverage_engine.c tests/test_fuzz_engine.c tinyhttp.c -fprofile-arcs -ftest-coverage $(LFLAGS) report: diff --git a/examples/iocp_server_with_engine.c b/examples/iocp_server_with_engine.c new file mode 100644 index 0000000..8f4d7a0 --- /dev/null +++ b/examples/iocp_server_with_engine.c @@ -0,0 +1,226 @@ +#include +#include +#include + +#include "../tinyhttp.h" + +#define MAX_CLIENTS (1<<10) + +// Callback used by the engine to manage dynamic memory +static void *memfunc(HTTP_MemoryFuncTag tag, + void *ptr, int len, void *data) +{ + (void) data; + switch (tag) { + + case HTTP_MEMFUNC_MALLOC: + return malloc(len); + + case HTTP_MEMFUNC_FREE: + free(ptr); + return NULL; + } + return NULL; +} + +static SOCKET start_accept(LPFN_ACCEPTEX AcceptEx, SOCKET listen_fd, OVERLAPPED *accept_ov, char *buf, int bufsize) +{ + SOCKET accept_target = socket(AF_INET, SOCK_STREAM, 0); + if (accept_target == INVALID_SOCKET) + return INVALID_SOCKET; + + unsigned long num; + memset(accept_ov, 0, sizeof(OVERLAPPED)); + BOOL ok = AcceptEx(listen_fd, accept_target, buf, bufsize - ((sizeof(struct sockaddr_in) + 16) * 2), + sizeof(struct sockaddr_in) + 16, + sizeof(struct sockaddr_in) + 16, + &num, accept_ov); + if (!ok && WSAGetLastError() != ERROR_IO_PENDING) { + closesocket(accept_target); + return INVALID_SOCKET; + } + + return accept_target; +} + +static void start_recv_or_send(SOCKET sock, OVERLAPPED *recv_ov, OVERLAPPED *send_ov, HTTP_Engine *eng) +{ + HTTP_EngineState state = http_engine_state(eng); + + if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) { + + int cap; + char *dst = http_engine_recvbuf(eng, &cap); + + memset(recv_ov, 0, sizeof(OVERLAPPED)); + int ok = ReadFile((HANDLE) sock, dst, cap, NULL, recv_ov); + if (!ok && GetLastError() != ERROR_IO_PENDING) + http_engine_close(eng); + + } else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) { + + int len; + char *src = http_engine_sendbuf(eng, &len); + + memset(send_ov, 0, sizeof(OVERLAPPED)); + int ok = WriteFile((HANDLE) sock, src, len, NULL, send_ov); + if (!ok && GetLastError() != ERROR_IO_PENDING) + http_engine_close(eng); + } +} + +int main(void) +{ + // Interface and port the server will be listening on. + char *addr = "127.0.0.1"; + int port = 8080; + + // Initialize the winsock subsystem + WSADATA wd; + if (WSAStartup(MAKEWORD(2, 2), &wd)) + return -1; + + // Create the I/O completion port object + HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); + if (iocp == INVALID_HANDLE_VALUE) + return -1; + + // Create the listening socket + SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (listen_fd == INVALID_SOCKET) + return -1; + + // Register the socket into the IOCP + if (CreateIoCompletionPort((HANDLE) listen_fd, iocp, 0, 0) == NULL) + return -1; + + // Ignore the cooldown time for the bound interface to + // avoid that annoying "address in use" error + int reuse = 1; + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse)); + + // Fill out the address struct + struct sockaddr_in bind_buf; + { + struct in_addr addr_buf; + if (inet_pton(AF_INET, addr, &addr_buf) != 1) + return -1; + + bind_buf.sin_family = AF_INET; + bind_buf.sin_port = htons(port); + bind_buf.sin_addr = addr_buf; + memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero)); + } + + // Associate the listening socket to the interface + if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) + return -1; + + // Allow incoming connections + if (listen(listen_fd, 32) < 0) + return -1; + + // Get the AcceptEx function pointer + LPFN_ACCEPTEX AcceptEx = NULL; + GUID GuidAcceptEx = WSAID_ACCEPTEX; + unsigned long num; + int ret = WSAIoctl(listen_fd, + SIO_GET_EXTENSION_FUNCTION_POINTER, + &GuidAcceptEx, sizeof(GuidAcceptEx), + &AcceptEx, sizeof(AcceptEx), + &num, NULL, NULL); + if (ret == SOCKET_ERROR) + return -1; + + OVERLAPPED accept_ov; + char accept_buf[2 * (sizeof(struct sockaddr_in) + 16)]; + SOCKET accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf)); + if (accept_target == INVALID_SOCKET) + return -1; + + OVERLAPPED recv_overlapped[MAX_CLIENTS]; + OVERLAPPED send_overlapped[MAX_CLIENTS]; + SOCKET sockets[MAX_CLIENTS]; + HTTP_Engine engs[MAX_CLIENTS]; + + for (int i = 0; i < MAX_CLIENTS; i++) + sockets[i] = INVALID_SOCKET; + + for (;;) { + DWORD timeout = INFINITE; + + DWORD transferred; + ULONG_PTR key; + OVERLAPPED *overlapped; + BOOL result = GetQueuedCompletionStatus(iocp, &transferred, &key, &overlapped, timeout); + if (!result && overlapped == NULL) { + if (GetLastError() == WAIT_TIMEOUT) + continue; // Go back to waiting + return -1; // Error + } + + if (overlapped == &accept_ov) { + // A new client connected + + int i = 0; + while (i < MAX_CLIENTS && sockets[i] != INVALID_SOCKET) + i++; + + if (i == MAX_CLIENTS) + closesocket(accept_target); // Server limit reached + else { + + sockets[i] = accept_target; + http_engine_init(&engs[i], 0, memfunc, NULL); + + // Register the socket into the IOCP + if (CreateIoCompletionPort((HANDLE) sockets[i], iocp, i, 0) == NULL) + return -1; + + start_recv_or_send(sockets[i], &recv_overlapped[i], &send_overlapped[i], &engs[i]); + + // Check that the recv or send operation was started. + // If now, remove the connection + if (http_engine_state(&engs[i]) == HTTP_ENGINE_STATE_CLIENT_CLOSED) { + closesocket(sockets[i]); + sockets[i] = INVALID_SOCKET; + http_engine_free(&engs[i]); + } + } + + accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf)); + if (accept_target == INVALID_SOCKET) + return -1; + + // Go back to waiting + continue; + } + + // Complete the current operation + if (0) {} + else if (overlapped == &recv_overlapped[key]) http_engine_recvack(&engs[key], transferred); + else if (overlapped == &send_overlapped[key]) http_engine_sendack(&engs[key], transferred); + + if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) { + + // See blocking_server_with_engine.c to learn about how to + // build a response + http_engine_status(&engs[key], 200); + http_engine_body(&engs[key], "Hello, world!", -1); + http_engine_done(&engs[key]); + } + + start_recv_or_send(sockets[key], + &recv_overlapped[key], + &send_overlapped[key], + &engs[key]); + + if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_CLOSED) { + http_engine_free(&engs[key]); + closesocket(sockets[key]); + sockets[key] = INVALID_SOCKET; + } + } + + return 0; +} \ No newline at end of file