From 385bbc7fb61a9f9a332158d603683e9e0430cdec Mon Sep 17 00:00:00 2001 From: Eggert Jung Date: Sun, 7 Dec 2025 11:55:39 +0100 Subject: [PATCH] tcp --- test.c | 261 +++++++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 182 insertions(+), 79 deletions(-) diff --git a/test.c b/test.c index add4f44..cbbe0d5 100644 --- a/test.c +++ b/test.c @@ -1,96 +1,153 @@ /* ------------------------------------------------------------- - * framebuffer_demo.c - * ------------------------------------------------------------- - * A tiny example that: - * * allocates a 600×400 RGB‑888 framebuffer in RAM, - * * fills it with a demo pattern, - * * opens an SDL2 window, - * * streams the framebuffer to the window each frame. + * framebuffer_demo_tcp.c + * + * * 600×400 ARGB8888 framebuffer in RAM + * * SDL2 window that displays the framebuffer + * * Simple TCP server (single client) that streams raw pixel data * * Build (Linux/macOS): - * gcc -Wall -O2 -std=c11 framebuffer_demo.c -lSDL2 -o framebuffer_demo + * gcc -Wall -Wextra -pedantic -std=c11 framebuffer_demo_tcp.c \ + * -lSDL2 -lpthread -o framebuffer_demo_tcp * * Run: - * ./framebuffer_demo + * ./framebuffer_demo_tcp # opens a window and listens on 0.0.0.0:12345 + * + * Test client (Unix‑like): + * # Send a static colour (red) for one frame + * dd if=/dev/zero bs=1 count=$((600*400*4)) | tr \\0 \\377 > red.bin + * cat red.bin | nc 127.0.0.1 12345 * * ------------------------------------------------------------- */ #include #include #include #include +#include // memcpy, memset +#include // close, read, write +#include // fcntl +#include +#include +#include +#include +#include // select() +#include /* ------------------------------------------------------------- * Configuration * ------------------------------------------------------------- */ -#define WIDTH 600 -#define HEIGHT 400 +#define WIDTH 600 +#define HEIGHT 400 +#define LISTEN_PORT 12345 // change if you like +#define BACKLOG 1 // only one pending connection /* ------------------------------------------------------------- * Simple pixel format helpers * ------------------------------------------------------------- */ -/* The framebuffer will be stored as 32‑bit ARGB (0xAARRGGBB). */ -typedef Uint32 pixel_t; +typedef Uint32 pixel_t; // 0xAARRGGBB in memory (little‑endian) -/* Build a pixel from separate components (no gamma correction). */ -static inline pixel_t make_pixel(Uint8 r, Uint8 g, Uint8 b, Uint8 a) { - /* SDL uses little‑endian, so the order in memory is BGRA. - * Our 0xAARRGGBB layout works fine with SDL_UpdateTexture. */ - return (pixel_t)( (a << 24) | (r << 16) | (g << 8) | b ); +static inline pixel_t make_pixel(Uint8 r, Uint8 g, Uint8 b, Uint8 a) +{ + return (pixel_t)((a << 24) | (r << 16) | (g << 8) | b); } +#define MAKE_OPAQUE_PIXEL(r,g,b) make_pixel((r),(g),(b),0xFF) /* ------------------------------------------------------------- - * Global framebuffer + * Global framebuffer (contiguous block) * ------------------------------------------------------------- */ -static pixel_t framebuffer[HEIGHT][WIDTH]; +static pixel_t framebuffer[HEIGHT][WIDTH]; // 600*400*4 = 960 000 bytes /* ------------------------------------------------------------- - * Fill the framebuffer with a demo pattern + * Fill with a cute demo pattern (visible before any network data) * ------------------------------------------------------------- */ -static void init_framebuffer(void) +static void init_demo_pattern(void) { for (int y = 0; y < HEIGHT; ++y) { for (int x = 0; x < WIDTH; ++x) { - /* Example: a smooth red‑to‑green gradient */ - Uint8 r = (Uint8)((x * 255) / (WIDTH - 1)); /* left → right */ - Uint8 g = (Uint8)((y * 255) / (HEIGHT - 1)); /* top → bottom */ - Uint8 b = 0x80; /* constant bluish */ - framebuffer[y][x] = make_pixel(r, g, b, 255); + Uint8 r = (Uint8)((x * 255) / (WIDTH - 1)); + Uint8 g = (Uint8)((y * 255) / (HEIGHT - 1)); + Uint8 b = 0x80; + framebuffer[y][x] = MAKE_OPAQUE_PIXEL(r, g, b); } } } /* ------------------------------------------------------------- + * ---------- TCP SERVER ------------------------------------ + * ------------------------------------------------------------- */ + +/* Returns a non‑blocking listening socket, or -1 on error. */ +static int create_listen_socket(void) +{ + int lst = socket(AF_INET, SOCK_STREAM, 0); + if (lst < 0) { + perror("socket"); + return -1; + } + + /* Allow immediate reuse of the address (helps during rapid restarts). */ + int opt = 1; + setsockopt(lst, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(LISTEN_PORT), + .sin_addr = { .s_addr = INADDR_ANY } + }; + + if (bind(lst, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("bind"); + close(lst); + return -1; + } + + if (listen(lst, BACKLOG) < 0) { + perror("listen"); + close(lst); + return -1; + } + + /* Make the socket non‑blocking so that accept() never blocks. */ + int flags = fcntl(lst, F_GETFL, 0); + fcntl(lst, F_SETFL, flags | O_NONBLOCK); + + printf("[net] Listening on 0.0.0.0:%d (single client allowed)\n", LISTEN_PORT); + return lst; +} + +/* ------------------------------------------------------------- + * Helper: set a socket to non‑blocking mode + * ------------------------------------------------------------- */ +static void set_nonblocking(int fd) +{ + int flags = fcntl(fd, F_GETFL, 0); + if (flags != -1) fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} + +/* ------------------------------------------------------------- * Main entry point * ------------------------------------------------------------- */ int main(void) { + /* ---------- SDL initialisation -------------------------------- */ if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "SDL_Init error: %s\n", SDL_GetError()); return EXIT_FAILURE; } - /* --------------------------------------------------------- - * Create a window and a renderer (software accelerated is fine) - * --------------------------------------------------------- */ - SDL_Window *window = SDL_CreateWindow( - "Framebuffer Demo", - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - WIDTH, - HEIGHT, - SDL_WINDOW_SHOWN - ); + SDL_Window *window = SDL_CreateWindow("Framebuffer Demo – TCP Input", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + WIDTH, HEIGHT, + SDL_WINDOW_SHOWN); if (!window) { fprintf(stderr, "SDL_CreateWindow error: %s\n", SDL_GetError()); SDL_Quit(); return EXIT_FAILURE; } - SDL_Renderer *renderer = SDL_CreateRenderer( - window, - -1, - SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC - ); + SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, + SDL_RENDERER_ACCELERATED | + SDL_RENDERER_PRESENTVSYNC); if (!renderer) { fprintf(stderr, "SDL_CreateRenderer error: %s\n", SDL_GetError()); SDL_DestroyWindow(window); @@ -98,17 +155,10 @@ int main(void) return EXIT_FAILURE; } - /* --------------------------------------------------------- - * Create a texture that will hold the framebuffer. - * SDL_PIXELFORMAT_ARGB8888 matches our pixel_t layout. - * --------------------------------------------------------- */ - SDL_Texture *texture = SDL_CreateTexture( - renderer, - SDL_PIXELFORMAT_ARGB8888, - SDL_TEXTUREACCESS_STREAMING, - WIDTH, - HEIGHT - ); + SDL_Texture *texture = SDL_CreateTexture(renderer, + SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, + WIDTH, HEIGHT); if (!texture) { fprintf(stderr, "SDL_CreateTexture error: %s\n", SDL_GetError()); SDL_DestroyRenderer(renderer); @@ -117,57 +167,109 @@ int main(void) return EXIT_FAILURE; } - /* --------------------------------------------------------- - * Initialise the framebuffer with example data. - * --------------------------------------------------------- */ - init_framebuffer(); + /* ---------- Initialise demo framebuffer ----------------------- */ + init_demo_pattern(); + + /* ---------- Initialise networking ----------------------------- */ + int listen_sock = create_listen_socket(); + if (listen_sock < 0) { + /* Continue without network – the window will just show the demo. */ + fprintf(stderr, "[net] Continuing without network input.\n"); + } + + int client_sock = -1; // -1 means “no client connected” + size_t bytes_needed = WIDTH * HEIGHT * sizeof(pixel_t); + size_t recv_offset = 0; // how many bytes of the current frame we already have - /* --------------------------------------------------------- - * Main loop - * --------------------------------------------------------- */ bool quit = false; SDL_Event ev; + /* ---------- Main loop ---------------------------------------- */ while (!quit) { - /* ----- Event handling --------------------------------- */ + /* ---- 1) Process SDL events (non‑blocking) ------------- */ while (SDL_PollEvent(&ev)) { - if (ev.type == SDL_QUIT) { + if (ev.type == SDL_QUIT) quit = true; + else if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_ESCAPE) quit = true; - } else if (ev.type == SDL_KEYDOWN) { - /* Press Escape to quit */ - if (ev.key.keysym.sym == SDLK_ESCAPE) - quit = true; + } + + /* ---- 2) Accept a client if we have none yet ------------- */ + if (listen_sock >= 0 && client_sock < 0) { + struct sockaddr_in remote; + socklen_t remote_len = sizeof(remote); + int tmp = accept(listen_sock, + (struct sockaddr *)&remote, + &remote_len); + if (tmp >= 0) { + client_sock = tmp; + set_nonblocking(client_sock); + recv_offset = 0; // start a fresh frame + char ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &remote.sin_addr, ip, sizeof(ip)); + printf("[net] Client connected from %s:%d\n", ip, ntohs(remote.sin_port)); + } else if (errno != EAGAIN && errno != EWOULDBLOCK) { + perror("[net] accept"); } } - /* ----- Update texture from the framebuffer ------------ */ + /* ---- 3) Read raw pixel data from the client ------------- */ + if (client_sock >= 0) { + /* Try to read the remainder of the current frame. */ + size_t still_needed = bytes_needed - recv_offset; + ssize_t n = recv(client_sock, + ((Uint8 *)framebuffer) + recv_offset, + still_needed, + 0); + if (n > 0) { + recv_offset += (size_t)n; + if (recv_offset == bytes_needed) { + /* Full frame received – it will be displayed on the next render pass. */ + recv_offset = 0; // start collecting the next frame + } + } else if (n == 0) { + /* Client performed an orderly shutdown. */ + printf("[net] Client disconnected.\n"); + close(client_sock); + client_sock = -1; + } else if (n < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + perror("[net] recv"); + close(client_sock); + client_sock = -1; + } + /* If EAGAIN/EWOULDBLOCK we simply have no more data right now. */ + } + } + + /* ---- 4) Copy framebuffer into texture ------------------- */ void *tex_pixels; - int tex_pitch; // bytes per row + int tex_pitch; if (SDL_LockTexture(texture, NULL, &tex_pixels, &tex_pitch) != 0) { fprintf(stderr, "SDL_LockTexture error: %s\n", SDL_GetError()); break; } - /* Copy line‑by‑line because the pitch may differ from WIDTH*4 */ + /* The texture pitch may be larger than WIDTH*4, so copy line‑by‑line. */ for (int y = 0; y < HEIGHT; ++y) { - memcpy( - (Uint8 *)tex_pixels + y * tex_pitch, - framebuffer[y], - WIDTH * sizeof(pixel_t) - ); + memcpy((Uint8 *)tex_pixels + y * tex_pitch, + framebuffer[y], + WIDTH * sizeof(pixel_t)); } SDL_UnlockTexture(texture); - /* ----- Render ------------------------------------------ */ + /* ---- 5) Render ------------------------------------------ */ SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); - /* The loop runs at the monitor refresh rate thanks to VSYNC. */ + /* VSYNC in the renderer caps the loop to the monitor refresh rate. */ } - /* --------------------------------------------------------- + /* ------------------------------------------------------------- * Cleanup - * --------------------------------------------------------- */ + * ------------------------------------------------------------- */ + if (client_sock >= 0) close(client_sock); + if (listen_sock >= 0) close(listen_sock); + SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); @@ -175,3 +277,4 @@ int main(void) return EXIT_SUCCESS; } +