mitchellh / libxev

libxev is a cross-platform, high-performance event loop that provides abstractions for non-blocking IO, timers, events, and more and works on Linux (io_uring or epoll), macOS (kqueue), and Wasm + WASI. Available as both a Zig and C API.
MIT License
1.97k stars 65 forks source link

Need more examples: C Server #62

Open kassane opened 1 year ago

kassane commented 1 year ago

Hi @mitchellh,

Nice hard work (for you and all your collaborators) to make this library.

I have been playing with the xev api for C code on linux today. However, some trouble was encountered when running the same code with or without xev.

Client: curl 127.0.0.1:8000.

Without xev

C code - server ```c #include #include #include #include #include #include #include #define PORT 8000 // The port number for the server to listen on int main() { int server_socket, client_socket; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len = sizeof(client_addr); // Create a socket server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == -1) { perror("Socket creation failed"); exit(1); } // Bind the socket to an IP address and port server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; // Listen on all available network interfaces server_addr.sin_port = htons(PORT); if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("Socket binding failed"); close(server_socket); exit(1); } // Listen for incoming connections if (listen(server_socket, 10) == -1) { perror("Listen failed"); close(server_socket); exit(1); } printf("Server listening on port %d...\n", PORT); while (1) { // Accept a client connection client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len); if (client_socket == -1) { perror("Accept failed"); continue; // Continue listening for other connections } printf("Connection accepted from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // Handle the client connection (in this example, we simply echo back received data) char buffer[1024]; ssize_t bytes_received; while ((bytes_received = recv(client_socket, buffer, sizeof(buffer), 0)) > 0) { buffer[bytes_received] = '\0'; // Null-terminate the received data printf("Received: %s", buffer); // Echo back to the client send(client_socket, buffer, bytes_received, 0); } // Close the client socket close(client_socket); printf("Connection closed by %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); } // Close the server socket (this part will not be reached in this example) close(server_socket); return 0; } ```

Output

$> ./server_example 
Server listening on port 8000...
Connection accepted from 127.0.0.1:42220
Received: GET / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: curl/8.2.1
Accept: */*

Connection closed by 127.0.0.1:42220

With xev

C code - server ```c #include "xev.h" #include #include #include #include #include #include #define PORT 8000 // The port number for the server to listen on void on_connection(xev_loop* loop, xev_completion* c, int result, void* userdata) { int server_socket = *(int*)userdata; int client_socket = accept(server_socket, NULL, NULL); if (client_socket == -1) { perror("Accept failed"); return; } char buffer[1024]; ssize_t bytes_received; while ((bytes_received = recv(client_socket, buffer, sizeof(buffer), 0)) > 0) { buffer[bytes_received] = '\0'; // Null-terminate the received data printf("Received: %s", buffer); // Echo back to the client send(client_socket, buffer, bytes_received, 0); } close(client_socket); } int main() { xev_loop loop; if (xev_loop_init(&loop) != 0) { fprintf(stderr, "Failed to initialize event loop\n"); return 1; } int server_socket, client_socket; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len = sizeof(client_addr); // Create a socket server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == -1) { perror("Socket creation failed"); exit(1); } // Bind the socket to an IP address and port server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; // Listen on all available network interfaces server_addr.sin_port = htons(PORT); if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("Socket binding failed"); close(server_socket); exit(1); } // Listen for incoming connections if (listen(server_socket, 10) == -1) { perror("Listen failed"); close(server_socket); exit(1); } printf("Server listening on port %d...\n", PORT); // Pass the server_socket as user data to the connection handler int user_data = server_socket; xev_completion completion; xev_completion_zero(&completion); xev_threadpool_task task; xev_threadpool_task_init(&task, (xev_task_cb)on_connection); xev_threadpool_batch batch; xev_threadpool_batch_init(&batch); xev_threadpool_batch_push_task(&batch, &task); xev_threadpool threadpool; xev_threadpool_init(&threadpool, NULL); xev_threadpool_schedule(&threadpool, &batch); xev_threadpool_deinit(&threadpool); xev_loop_run(&loop, XEV_RUN_UNTIL_DONE); xev_loop_deinit(&loop); close(server_socket); return 0; } ```

Output

$> ./server_example 
Server listening on port 8000...
Accept failed: Bad file descriptor

hold client, needed Ctrl+C to stopping.


Note: gcc get errors on data array on header.

include/xev.h:36:44: error: variably modified ‘data’ at file scope
   36 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_LOOP - sizeof(XEV_ALIGN_T)]; } xev_loop;
      |                                            ^~~~
include/xev.h:37:44: error: variably modified ‘data’ at file scope
   37 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_COMPLETION - sizeof(XEV_ALIGN_T)]; } xev_completion;
      |                                            ^~~~
include/xev.h:38:44: error: variably modified ‘data’ at file scope
   38 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_WATCHER - sizeof(XEV_ALIGN_T)]; } xev_watcher;
      |                                            ^~~~
include/xev.h:39:44: error: variably modified ‘data’ at file scope
   39 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL - sizeof(XEV_ALIGN_T)]; } xev_threadpool;
      |                                            ^~~~
include/xev.h:40:44: error: variably modified ‘data’ at file scope
   40 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL_BATCH - sizeof(XEV_ALIGN_T)]; } xev_threadpool_batch;
      |                                            ^~~~
include/xev.h:41:44: error: variably modified ‘data’ at file scope
   41 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL_TASK - sizeof(XEV_ALIGN_T)]; } xev_threadpool_task;
      |                                            ^~~~
include/xev.h:42:44: error: variably modified ‘data’ at file scope
   42 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL_CONFIG - sizeof(XEV_ALIGN_T)]; } xev_threadpool_config;
mitchellh commented 1 year ago

Thanks, the C API is admittedly anemic right now and not well maintained beyond extremely basic tested usage. I'm sure there are many improvements to be made here.