Mercurial
changeset 132:7a63e41a21fb
[Seobeo] Added debug targets.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 09 Jan 2026 08:23:54 -0800 |
| parents | b230a743a01e |
| children | 902e29c38d66 |
| files | mrjunejune/BUILD mrjunejune/main.c mrjunejune/src/blog/websocket-demystified/index.md seobeo/BUILD seobeo/s_web.c seobeo/seobeo_internal.h |
| diffstat | 6 files changed, 181 insertions(+), 13 deletions(-) [+] |
line wrap: on
line diff
--- a/mrjunejune/BUILD Fri Jan 09 07:42:04 2026 -0800 +++ b/mrjunejune/BUILD Fri Jan 09 08:23:54 2026 -0800 @@ -41,7 +41,15 @@ data = [":src_files"], ) -# Rlease bundle +# Debug build with verbose logging +cc_binary( + name = "mrjunejune_server_debug", + srcs = ["main.c"], + deps = ["//seobeo:seobeo_tcp_server_ws_debug"], + data = [":src_files"], +) + +# Release bundle bundle( name = "mrjunejune_server_bundle", binary = ":mrjunejune_server",
--- a/mrjunejune/main.c Fri Jan 09 07:42:04 2026 -0800 +++ b/mrjunejune/main.c Fri Jan 09 08:23:54 2026 -0800 @@ -1,4 +1,4 @@ -#define SEOBEO_ENABLE_DEBUG +// Debug mode is now controlled by build target: use //seobeo:seobeo_tcp_server_ws_debug #include "seobeo/seobeo.h" #include <time.h>
--- a/mrjunejune/src/blog/websocket-demystified/index.md Fri Jan 09 07:42:04 2026 -0800 +++ b/mrjunejune/src/blog/websocket-demystified/index.md Fri Jan 09 08:23:54 2026 -0800 @@ -4,14 +4,20 @@ Today, it’s the standard for everything from chat apps to LLM interfaces, where the model streams bytes back to you one token at a time as it predicts the next word. -Most developers just grab a library like [ws](https://github.com/websockets/ws) for Node.js or [websockets](https://websockets.readthedocs.io/) for Python and call it a day. But many don’t realize the underlying mechanism is actually pretty simple to implement yourself in a day or so. Let's look at how to build it from scratch. +Most developers just grab a library like [ws](https://github.com/websockets/ws) for Node.js or [websockets](https://websockets.readthedocs.io/) for Python and call it a day. But many don’t realize the underlying mechanism is actually pretty simple to implement yourself in a day or so. Also they misunderstand what websocket actually do. Let's look at how to build it from scratch and debunk myths. + +### The "65,535" Myth + +Before we write a single line of code, Let's talk about common myth. You’ll often hear developers say, *"A server can only handle 65,535 WebSocket connections because there are only 65,535 ports."* + +If this were true, Discord or Slack would need millions of separate IP addresses just to function. The confusion comes from the 16-bit size of the TCP port field, but a connection isn't defined by a port alone. You will see in this blog. --- ## Requirements * Ability to type -* Half a brain +* Half a brain to real mechanics, not the surface-level myths. * A computer --- @@ -31,7 +37,8 @@ To start the upgrade from HTTP to WebSocket, the client sends a standard GET request but with some very specific headers. -<div class="center"> <img src="/public/white-noise-grass.png" /> </div> +<div class="center"> <img src="/public/web-socket-header.webp" /> </div> + I’m assuming you know how HTTP works. If not, you can open a developer tool by right clicking on your browser and seeing into network tab and refershign the page. The only interesting values here is the `Sec-WebSocket-Key`. This key is usually a 16-byte random value encoded in **Base64**. @@ -124,13 +131,25 @@ Seobeo_WebSocket_Server_Connection *p_conn = malloc(sizeof(Seobeo_WebSocket_Server_Connection)); memset(p_conn, 0, sizeof(Seobeo_WebSocket_Server_Connection)); -p_conn->p_handle = p_handle; +p_conn->p_handle = p_handle; // file descriptor p_conn->is_active = TRUE; p_conn->fragment_capacity = 4096; p_conn->fragment_buffer = malloc(p_conn->fragment_capacity); ``` +### Wait, what is the p_handle or file descriptor here?? + +The File Descriptor (FD) is the internal ID badge your server assigns to a connection. The OS identifies a unique connection via a 4-tuple; Source IP, Source Port, Destination IP, Destination Port. You can think of the OS as a giant hashmap that links these 4-tuples to an integer (the FD). + +So the limits are from the number of FD and RAM capactiy. You can check your system's FD limit with: + +```bash +ulimit -n +``` + +Now, we have debunked this myth. Let's see how the protocol actually works. + --- ## Frame-Based Protocols
--- a/seobeo/BUILD Fri Jan 09 07:42:04 2026 -0800 +++ b/seobeo/BUILD Fri Jan 09 08:23:54 2026 -0800 @@ -341,7 +341,7 @@ visibility = ["//visibility:public"], ) -# Names I often use +# Names I often use alias( name = "seobeo_server", actual = ":seobeo_tcp_server", @@ -353,6 +353,130 @@ actual = ":seobeo", visibility = ["//visibility:public"], ) + +# ============================================================================ +# Debug Builds (with SEOBEO_ENABLE_DEBUG defined) +# ============================================================================ + +# TCP Server with HTTP + WebSocket (DEBUG) +alias( + name = "seobeo_tcp_server_ws_debug", + actual = select({ + "//config:macos": ":seobeo_tcp_server_ws_debug_macos", + "//config:linux": ":seobeo_tcp_server_ws_debug_linux", + "//conditions:default": ":seobeo_tcp_server_ws_debug_linux", + }), + visibility = ["//visibility:public"], +) + +cc_library( + name = "seobeo_tcp_server_ws_debug_macos", + srcs = [ + "s_network.c", + "s_web.c", + "s_logging.c", + "s_ssl.c", + "s_websocket_common.c", + "s_websocket_server.c", + "os/s_macos_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + defines = ["SEOBEO_WEBSOCKET_SERVER", "SEOBEO_ENABLE_DEBUG"], + target_compatible_with = [ + "@platforms//os:osx", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "seobeo_tcp_server_ws_debug_linux", + srcs = [ + "s_network.c", + "s_web.c", + "s_logging.c", + "s_ssl.c", + "s_websocket_common.c", + "s_websocket_server.c", + "os/s_linux_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + defines = ["SEOBEO_WEBSOCKET_SERVER", "SEOBEO_ENABLE_DEBUG"], + target_compatible_with = [ + "@platforms//os:linux", + ], + visibility = ["//visibility:public"], +) + +# All combined (DEBUG) +alias( + name = "seobeo_debug", + actual = select({ + "//config:macos": ":seobeo_debug_macos", + "//config:linux": ":seobeo_debug_linux", + "//conditions:default": ":seobeo_debug_linux", + }), + visibility = ["//visibility:public"], +) + +cc_library( + name = "seobeo_debug_macos", + srcs = [ + "s_network.c", + "s_web.c", + "s_logging.c", + "s_ssl.c", + "s_http_client.c", + "s_websocket_common.c", + "s_websocket.c", + "s_websocket_server.c", + "snapshot_creator.c", + "os/s_macos_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + defines = ["SEOBEO_WEBSOCKET_SERVER", "SEOBEO_ENABLE_DEBUG"], + target_compatible_with = [ + "@platforms//os:osx", + ], + visibility = ["//visibility:public"], +) + +cc_library( + name = "seobeo_debug_linux", + srcs = [ + "s_network.c", + "s_web.c", + "s_logging.c", + "s_ssl.c", + "s_http_client.c", + "s_websocket_common.c", + "s_websocket.c", + "s_websocket_server.c", + "snapshot_creator.c", + "os/s_linux_edge.c", + ], + hdrs = [":seobeo_hdrs"], + deps = [ + "//dowa:dowa", + "@openssl//:ssl", + ], + defines = ["SEOBEO_WEBSOCKET_SERVER", "SEOBEO_ENABLE_DEBUG"], + target_compatible_with = [ + "@platforms//os:linux", + ], + visibility = ["//visibility:public"], +) # ============================================================================ # Testing Utilities # ============================================================================
--- a/seobeo/s_web.c Fri Jan 09 07:42:04 2026 -0800 +++ b/seobeo/s_web.c Fri Jan 09 08:23:54 2026 -0800 @@ -189,15 +189,21 @@ if (p_file_kv) { - // File is cached - file_content = ((Seobeo_Cache_Entry*)p_file_kv)->value; - body_size = strlen(file_content); + // File is cached - use stored size for binary file support + Seobeo_Cached_File *cached = ((Seobeo_Cache_Entry*)p_file_kv)->value; + file_content = cached->content; + body_size = cached->size; } else { file_content = Seobeo_Web_LoadFile(file_path, &body_size); if (file_content) - Dowa_HashMap_Push(p_html_cache, file_path, file_content); + { + Seobeo_Cached_File *cached = malloc(sizeof(Seobeo_Cached_File)); + cached->content = (char*)file_content; + cached->size = body_size; + Dowa_HashMap_Push(p_html_cache, file_path, cached); + } } if (!file_content) @@ -219,11 +225,16 @@ else if (strstr(file_path, ".js")) mime = "application/javascript"; else if (strstr(file_path, ".png")) mime = "image/png"; else if (strstr(file_path, ".jpg") || strstr(file_path, ".jpeg")) mime = "image/jpeg"; + else if (strstr(file_path, ".webp")) mime = "image/webp"; else if (strstr(file_path, ".gif")) mime = "image/gif"; else if (strstr(file_path, ".svg")) mime = "image/svg+xml"; else if (strstr(file_path, ".ico")) mime = "image/x-icon"; else if (strstr(file_path, ".json")) mime = "application/json"; else if (strstr(file_path, ".wasm")) mime = "application/wasm"; + else if (strstr(file_path, ".mp4")) mime = "video/mp4"; + else if (strstr(file_path, ".webm")) mime = "video/webm"; + else if (strstr(file_path, ".glb")) mime = "model/gltf-binary"; + else if (strstr(file_path, ".gltf")) mime = "model/gltf+json"; Seobeo_Log(SEOBEO_DEBUG, "File path: %s\nBody Size: %zu\n", file_path, body_size);
--- a/seobeo/seobeo_internal.h Fri Jan 09 07:42:04 2026 -0800 +++ b/seobeo/seobeo_internal.h Fri Jan 09 08:23:54 2026 -0800 @@ -50,8 +50,14 @@ atomic_bool destroyed; } Seobeo_Handle; -// HTML cache type: maps file paths (char*) to file contents (char*) -typedef Dowa_KV(char*, char*) Seobeo_Cache_Entry; +// Cached file entry with content and size (for binary file support) +typedef struct { + char *content; + size_t size; +} Seobeo_Cached_File; + +// HTML cache type: maps file paths (char*) to cached file entries (Seobeo_Cached_File*) +typedef Dowa_KV(char*, Seobeo_Cached_File*) Seobeo_Cache_Entry; typedef struct { Seobeo_Handle *srv;