{"id":18877298,"url":"https://github.com/pedrozappa/42_minitalk","last_synced_at":"2025-09-05T02:06:33.776Z","repository":{"id":226587554,"uuid":"759090929","full_name":"PedroZappa/42_minitalk","owner":"PedroZappa","description":"42 Project : minitalk","archived":false,"fork":false,"pushed_at":"2025-07-27T10:41:59.000Z","size":99891,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-27T13:31:17.005Z","etag":null,"topics":["42born2code","42projects","42school","bitwise","client","gdb","gdbinit","make","minitalk","minitalk42","server"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PedroZappa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-02-17T18:44:43.000Z","updated_at":"2025-07-27T10:42:03.000Z","dependencies_parsed_at":"2025-07-27T13:29:35.691Z","dependency_job_id":null,"html_url":"https://github.com/PedroZappa/42_minitalk","commit_stats":null,"previous_names":["pedrozappa/42_minitalk"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/PedroZappa/42_minitalk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PedroZappa%2F42_minitalk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PedroZappa%2F42_minitalk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PedroZappa%2F42_minitalk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PedroZappa%2F42_minitalk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PedroZappa","download_url":"https://codeload.github.com/PedroZappa/42_minitalk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PedroZappa%2F42_minitalk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273699712,"owners_count":25152286,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["42born2code","42projects","42school","bitwise","client","gdb","gdbinit","make","minitalk","minitalk42","server"],"created_at":"2024-11-08T06:18:08.068Z","updated_at":"2025-09-05T02:06:28.765Z","avatar_url":"https://github.com/PedroZappa.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"**Note** : If you found this repo you should stop for a second and [read this](https://atomys.me/en/s42-sunset-story/)\n\n\u003ca name=\"readme-top\"\u003e\u003c/a\u003e\n\u003cdiv align=\"center\"\u003e\n\n# minitalk\n\n\u003e A minimalistic implementation of a small data exchange program using UNIX signals. \n\n\u003cp\u003e\n    \u003cimg src=\"https://img.shields.io/badge/score-%20%2F%20100-success?style=for-the-badge\" /\u003e\n    \u003cimg src=\"https://img.shields.io/github/repo-size/PedroZappa/42_minitalk?style=for-the-badge\u0026logo=github\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/languages/count/PedroZappa/42_minitalk?style=for-the-badge\u0026logo=\" /\u003e\n    \u003cimg src=\"https://img.shields.io/github/languages/top/PedroZappa/42_minitalk?style=for-the-badge\" /\u003e\n    \u003cimg src=\"https://img.shields.io/github/last-commit/PedroZappa/42_minitalk?style=for-the-badge\" /\u003e\n\u003c/p\u003e\n\n___\n\n\u003c!-- \u003cimg alt=\"minitalk demo\" src=\"./video/minitalk.gif\" width=\"100%\" /\u003e --\u003e\n\n\u003cimg alt=\"minitalk demo\" src=\"./video/minitalk-demo.gif\" width=\"100%\" /\u003e\n\n\u003c!-- \u003cvideo controls autoplay=true loop=true width=\"100%\"\u003e --\u003e\n\u003c!-- \t\u003csource src=\"/video/minitalk-demo.mp4\"  --\u003e\n\u003c!-- \t\ttype=\"video/mp4\" /\u003e --\u003e\n\u003c!-- \u003c/video\u003e --\u003e\n\n___\n\n\u003ch3\u003eTable o'Contents\u003c/h3\u003e\n\n\u003c/div\u003e\n\n\u003c!-- mtoc-start --\u003e\n\n* [About 📌](#about-)\n  * [Mandatory Features](#mandatory-features)\n  * [Bonus Features](#bonus-features)\n* [Implementation 📜](#implementation-)\n  * [`t_protocol`](#t_protocol)\n  * [`server.c`](#serverc)\n    * [Initializing `sigaction`](#initializing-sigaction)\n    * [`ft_server_sighandler()`](#ft_server_sighandler)\n    * [Receiving Data](#receiving-data)\n    * [`ft_strlen_received()`](#ft_strlen_received)\n    * [`ft_print_msg()`](#ft_print_msg)\n    * [Printing the Message](#printing-the-message)\n  * [`client.c`](#clientc)\n    * [Initializing the Client's `sigaction`](#initializing-the-clients-sigaction)\n    * [`ft_send_msg()`](#ft_send_msg)\n  * [`ft_send.c`](#ft_sendc)\n    * [`ft_send_char()` \u0026 `ft_send_int()`](#ft_send_char--ft_send_int)\n    * [Sending Data ](#sending-data-)\n    * [`ft_send_bit()`](#ft_send_bit)\n  * [`ft_sigaction.c`](#ft_sigactionc)\n* [Usage 🏁](#usage-)\n* [Testing 🧪](#testing-)\n* [Appendix 📖](#appendix-)\n  * [`Unicode` Character Encoding](#unicode-character-encoding)\n    * [Variable Length Encoding](#variable-length-encoding)\n    * [Code Points](#code-points)\n    * [Grapheme Clusters](#grapheme-clusters)\n* [License](#license)\n\n\u003c!-- mtoc-end --\u003e\n\n___\n\n## About 📌\n\n\u003e The goal of this project is to develop a `client`-`server` communication program using **UNIX signals** only.\n\n### Mandatory Features\n\nThe mandatory implementation must behave as follows:\n\n* First the `server` must be started, which will generate a `pid` and print it to `stdout`.\n* The `client` should accept two parameters:\n\t* The `pid` of the `server`;\n\t* The `message` to be sent;\n* The `client` must send the `message` passed in as a parameter to the `server`.\n* Upon receiving the `message` the `server` must print it to `stdout` almost instantly.\n* The `server` must be able to receive `message`s from several different clients **in a row** without the need for a restart. (Note that Linux systems do NOT queue signals when a signal of the same type is already pending).\n* `client`-`server` communication must be done using `SIGUSR1` and `SIGUSR2` signals only.\n\n### Bonus Features\n\n* The `server` acknowledges receiving a message by sending back a signal to the `client`.\n* Support Unicode characters.\n\n___\n## Implementation 📜\n\n\u003e For this project I chose to implement both mandatory and bonus features together. The `server` and `client` can be found in the `server.c` and `client.c` files inside the `src` folder plus two additional files `ft_sigaction.c` and `ft_send.c` containing helper functions.\n\n___\n### `t_protocol`\n\nFor the sake of simplicity the program uses a **custom data type** `t_protocol` which holds all the data the server needs to perform its operations:\n```c\ntypedef struct s_protocol\n{\n\tint  bits;     // Number of bits received\n\tint  data;     // Received data (One integer and a sequence of chars)\n\tint  received; // Flag indicating if \"header\" data has been received\n\tchar *msg;     // Received message\n}\tt_protocol;\n```\n\n___\n### `server.c`\n\nTo implement the [server](https://github.com/PedroZappa/42_minitalk/blob/main/src/server.c)'s signal handling functionality I chose to use `sigaction()` over `signal()`.\n\n\u003e This is because `signal()` is deprecated due to its varying behaviour across UNIX versions, making it a **non-portable option**.\n\n\u003e [!Important]\n\u003e Both functions listen for a **user defined signals** and change their default **signal actions**. The main difference between these functions  is that `sigaction()` employs a specialized struct to store extra information, giving the user finer control over what they can do when handling a signal.\n\n___\n#### Initializing `sigaction`\n\nThe `server`'s **main()** function declares and initializes a `struct sigaction` variable called `sa`.\n```c\nstruct sigaction\tsa;\n\nsigemptyset(\u0026sa.sa_mask);\nsa.sa_sigaction = ft_server_sighandler;\nsa.sa_flags = SA_SIGINFO | SA_RESTART;\n```\n\n* `sa.sa_mask` specifies a mask of signals that should be ignored;\n* We use `sigemptyset()` to initialize a signal set `sa.sa_mask` with all signals excluded from the set;\n* `sa.sa_sigaction` is set to the function `ft_server_sighandler()`;\n* `sa.sa_flags` flag set has the bits for `SA_SIGINFO` and `SA_RESTART` turned on;\n\n\u003e [!Note]\n\u003e\n\u003e * `SA_SIGINFO` : gives the user access to extended signal information; This flag makes `sigaction()` switch where it looks for the custom signal handler, changing it from the `sa.sa_handler` member to `sa.sa_sigaction`.\n\u003e\n\u003e * `SA_RESTART` : provides BSD compatible behaviour allowing certain system calls to be restartable across signals.\n\n___\nThe `sa` struct is then passed to `ft_set_sigaction()` to initialize event handling for `SIGUSR1` and `SIGUSR2` signals.\n```c\nft_set_sigaction(\u0026sa);\n```\n\n\u003e [!Note]\n\u003e\n\u003e See [`ft_sigaction`](#ft_sigactionc) for more details on what `sigaction()` does.\n\nThen the `server` prints its `pid` to `stdout` and enters an infinite loop, listening for a signal to catch.\n```c\nft_print_pid();\nwhile (1)\n\tpause();\n```\n___\n#### `ft_server_sighandler()`\n```c\nstatic void\tft_server_sighandler(int sig, siginfo_t *info, void *context);\n```\n\n\u003e Any time `SIGUSR1` or `SIGUSR2` signal is received, `ft_server_sighandler()` is called.\n\n* All its local variables are static, therefore automatically initialized to 0, except for `client_pid`, which we initialize to -1 to mean an error condition.\n```c\nstatic t_protocol   server;\nstatic int          i;\nstatic pid_t        client_pid = -1;\n\nusleep(PAUSE);\n(void)context;\n```\n\n* The server signal handler waits for `PAUSE` (100 microseconds) before it starts receiving data.\n\n* `context` is type cast to `void *` to suppress compiler warnings since we do not need to use it.\n```c\nif (client_pid == -1)\n    client_pid = info-\u003esi_pid;\nelse if (client_pid != info-\u003esi_pid)\n{\n    if (server.msg)\n        free(server.msg);\n    ft_perror_exit(\"Client PID does not match\\n\");\n}\n```\n* If `client_pid` is -1, it means that the `server` hasn't connected to a `client` yet so the program sets `client_pid` to `info-\u003esi_pid`, the `pid` of the `client` currently connecting.\n\n* If `client_pid` is not equal to the `pid` of the current `client`, the `server` frees the allocated message and prints an error and exits.\n```c\nif (!server.bits)\n\tserver.data = 0;\n```\n* If `server.bits` is 0, it means that the `server` has not received any data yet so the program sets `server.data` to 0 to prepare to receive the incoming data.\n\n___\n#### Receiving Data\n\nThe `server` first receives an integer as \"header information\" specifying the length in bytes of the message about to be transferred, then come the actual bits of the message.\n\n\u003e To store the bits according to the data type being received the following bitwise operations and conditionals are employed:\n```c\nif ((sig == SIGUSR2) \u0026\u0026 !server.received)\n\tserver.data |= 1 \u003c\u003c (((sizeof(int) * 8) - 1) - server.bits);\nelse if ((sig == SIGUSR2) \u0026\u0026 server.received)\n\tserver.data |= 1 \u003c\u003c (((sizeof(char) * 8) - 1) - server.bits);\n```\n\n* The conditional statements make sure that the first 32 bits of incoming data are saved in a space that fits an `int`.\n\n* The bitwise operators `|` (OR) and `\u003c\u003c` (Left-Shift) are used together to set the received bits in their right place in memory.\n\n* After this `int` is received the `server` starts storing the following inbound bits into `char` sized chunks of memory.\n\n\u003e [!Note]\n\u003e\n\u003e These memory-writing bitwise operations only happen when a `SIGUSR2` is received.\n\u003e\n\u003e Any time a `SIGUSR1` is caught, the server simply acknowledges by sending back a `SIGUSR1` to the `client`.\n\u003e\n\u003e * Because the memory in `server.data` is initially set to 0, the `server` only needs to act when a 1 is received, and flip the appropriate bit in its right place in memory.\n\n\u003e [!Important]\n\u003e\n\u003e `SIGUSR1` and `SIGUSR2` are therefore used to signify 0 and 1 respectively.\n\n___\n#### `ft_strlen_received()`\n```c\nstatic void\tft_strlen_received(t_protocol *server);\n```\n\n\u003e Once the `int` has been received the conditions for triggering the code block inside `ft_strlen_received()` are met:\n```c\nif ((server-\u003ebits == (sizeof(int) * 8)) \u0026\u0026 !server-\u003ereceived) { ... }\n```\n\n* This function first sets the `server.received` flag to 1, signifying that the header data has been received. \n\nThe `server` prints the length of the message to `stdout`, then takes this value plus 1 (to account for the NULL terminator) and allocates memory for a message with that many bytes with `ft_calloc()` so that the memory is all set to zero:\n```c\nserver-\u003emsg = ft_calloc((server-\u003edata + 1), sizeof(char));\nif (!server-\u003emsg)\n\tft_perror_exit(\"ft_calloc() failed\\n\");\n```\n\nThe memory space for the message is then NULL terminated, and the `server-\u003ebits` are reset to 0 to prepare the server to receive the bits of the message.\n```c\nserver-\u003emsg[server-\u003edata] = '\\0';\nserver-\u003ebits = 0;\n```\nThe function ends and the `server` continues receiving the message bit by bit until every `char` in the message has been transferred successfully.\n\n___\n#### `ft_print_msg()`\n```c\nstatic void\tft_print_msg(t_protocol *server, int *i, pid_t *pid);\n```\n\nOnce 8 bits have been received and the header information has already been transferred, the first layer of logic is triggered:\n```c\nif ((server-\u003ebits == 8) \u0026\u0026 server-\u003ereceived) { ... }\n```\n* The received byte stored in `server.data` is copied to the `i`-th index of `server-\u003emsg`.\n\nThen `i` is incremented so that when indexed `server-\u003emsg[i]` points to the next byte in memory where the next `char` or `Unicode` segment (**code point**) is gonna be stored.\n```c\nserver-\u003emsg[*i] = server-\u003edata;\n++(*i);\n```\n\n\u003e [!Important]\n\u003e For more about `Unicode` check the Appendix, [`Unicode` Character Encoding](#unicode-character-encoding).\n\nNotice that `server.bits` is reset to 0 after the `char` has been stored, in preparation to receive the next.\n```c\nserver-\u003ebits = 0;\n```\n\nAnd so the `server` receives each byte of the message until the whole message has been received. \n\n___\n#### Printing the Message\n\nThe server knows all the data in the message has been received when the current `server.data` value is the NULL terminator.\n```c\nif (server-\u003edata == '\\0') { ... }\n```\n\n* The server then prints the message to `stdout` followed by the `server`'s\n`pid`. \n```c\nft_printf(\"Message:\\n%s%s%s\\n\", GRN, server-\u003emsg, NC)\nft_print_pid();\n```\n\n\u003e Now the `server` performs some clean up to prepare to receive the next message.\n\n```c\nfree(server-\u003emsg);\nserver-\u003emsg = NULL;\nserver-\u003ereceived = 0;\n*i = 0;\n*pid = -1;\n```\n* Since we are done with the `server.msg`, we free the memory space allocated to store it.\n* We set the `server-\u003emsg` pointer to NULL.\n* And set `i` and `server-\u003ereceived` flag to 0.\n* We reset the `pid` to -1 to prepare the server to receive the next message from a different `pid`.\n\n```c\nft_send_bit(pid, 1, 0);\n```\n\nFinally we send a bit back to the `client` to signal that the message has been received.\n\n___\n### `client.c`\n\nBefore starting operations the [client](https://github.com/PedroZappa/42_minitalk/blob/main/src/client.c) must check if its input arguments are valid.\n```c\nif (argc != 3)\n\tft_perror_exit(\"Usage: ./client [PID] [message]\\n\");\nelse if (kill(ft_atoi(argv[1]), 0) \u003c 0)\n\tft_perror_exit(\"PID does not exist\\n\");\n```\n\n* It first checks if `argc` is not equal to 3, if so the program will print an error to `stderr` and exit.\n\n* Then checks if the `pid` of the server (`argv[1]`) is valid by test-calling `kill()` (with a zero instead of a signal identifier).\n\n* If it is NOT valid the program will also print an error to `stderr` and exit.\n___\n#### Initializing the Client's `sigaction`\n\nThe `client`, like the `server`, uses `sigaction()` to handle incoming UNIX signals:\n```c\nstruct sigaction\tsa;\n\nsigemptyset(\u0026sa.sa_mask);\nsa.sa_handler = ft_client_sighandler;\nsa.sa_flags = SA_RESTART;\nft_set_sigaction(\u0026sa);\n```\n\nA struct `sigaction` is declared as `sa` and:\n\n* Initializes its signal set `sa.sa_mask` with all signals excluded from the set using `sigemptyset()`;\n\n* `sa.sa_handler` is set to the function `ft_client_sighandler()`;\n\n* `sa.sa_flags` flag set has the bit for `SA_RESTART` turned on;\n\n* The `sa` struct is then passed into `ft_set_sigaction()` to set event handling for `SIGUSR1` and `SIGUSR2`;\n\n\u003e When the `client` event handler receives a signal, it checks if it is `SIGUSR1` or `SIGUSR2`:\n\u003e\n\u003e * If the incoming signal is `SIGUSR1` (Data Reception Acknowledgement), it prints a `*` to `stdout`.\n\u003e\n\u003e * Else if it receives `SIGUSR2` (Data Transmission Done), it prints a success message to `stdout` and exits.\n\nThe `client` then prints the `server`'s `pid` to `stdout` and calls `ft_send_msg()`:\n```c\nft_print_pid();\n...\nft_send_msg(ft_atoi(argv[1]), argv[2]);\n```\n___\n#### `ft_send_msg()`\n```c\nstatic void ft_send_msg(pid_t pid, char *msg);\n```\n\n* To keep track of the current index of the message being sent, a local integer variable `i` is created and initialized to 0.\n* Before sending the message we must first take the `message`'s length into the integer variable `msglen`.\n```c\nint i;\nint msglen;\n\ni = 0;\nmsglen = ft_strlen(msg);\nft_printf(\"%sOutbound msg's length = %d%s\\n\", CYN, msglen, NC);\n```\n\n* The message length is bit-by-bit using the function `ft_send_int`.\n```c\nft_send_int(pid, msglen);\n```\n\nThen it loops through the message and sends each character to the `server` bit-by-bit:\n```c\nft_printf(\"\\n%sSending Message%s\\n\", GRN, NC);\nwhile (msg[i] != '\\0')\n\tft_send_char(pid, msg[i++]);\nft_printf(\"\\n\");\nft_sep_color('0', '=', 28, GRN);\nft_printf(\"%sSending NULL Terminator\\n\", MAG, NC);\nft_sep_color('0', '=', 28, GRN);\n```\n\nThen all there's left to do is to send a NULL terminator to the `server` and terminate the message appropriately:\n```c\nft_send_char(pid, '\\0');\n```\n\n___\n### `ft_send.c`\n\nTo send `char`s and `int`s to the `server` two helper functions were implemented: `ft_send_char()` and `ft_send_int()`.\n\n___\n#### `ft_send_char()` \u0026 `ft_send_int()`\n```c\nvoid\tft_send_int(pid_t pid, int num);\nvoid\tft_send_char(pid_t pid, char c);\n```\n\nThese two functions work in similar ways.\n* They first initialize a `bitshift` integer variable with the size of the binary representation of the data type about to be sent:\n```c\nint\t\tbitshift;\n\nbitshift = ((sizeof(int) * 8) - 1);  // Prepare the server to receive 32 bits\n...\nbitshift = ((sizeof(char) * 8) - 1); // Prepare the server to receive 8 bits\n```\n\u003e [!Important]\n\u003e `bitshift` will be used to iterate through each byte of data being sent from the most significant (`MSB`) to the least significant bit (`LSB`).\n\n___\n#### Sending Data \n\nThe `client` enters a loop running from `bitshift` to 0:\n\n* It breaks the `char`/`int` into its individual bits;\n\n* Each bit is passed as an argument to `ft_send_bit()` where it triggers the appropriate signal and is sent to the `server`;\n\n* `bitshift` is decremented to move to the next bit of the binary representation of the `char`/`int`, from left to right;\n```c\nwhile (bitshift \u003e= 0)\n{\n\tbit = (num \u003e\u003e bitshift) \u0026 1; // Get the current bit\n\tft_send_bit(pid, bit, 1);    // Send the current bit\n\t--bitshift;                  // Move to the next bit\n}\n```\n___\n#### `ft_send_bit()`\n```c\nvoid\tft_send_bit(pid_t pid, char bit, char pause_flag);\n```\n\n`ft_send_bit()` sends information to the `server` bit-by-bit.\n\n* It simply checks if the passed `bit` is 1 or 0 and sends the appropriate signal using `kill()`.\n\n* If the call to `kill()` fails, the program writes an error message to `stderr` and exits.\n```c\nif (bit == 0)\n{\n\tif (kill(pid, SIGUSR1) \u003c 0)\n\t\tft_perror_exit(\"kill() failed sending SIGUSR1\\n\");\n}\nelse if (bit == 1)\n{\n\tif (kill(pid, SIGUSR2) \u003c 0)\n\t\tft_perror_exit(\"kill() failed sending SIGUSR2\\n\");\n}\n```\n\nIf the `pause_flag` is set to 1, the `server` waits for the next data chunk to be sent.\n```c\nif (pause_flag != 0)\n\tpause();\n```\n\n\u003e [!NOTE]\n\u003e\n\u003e This function is called with `pause_flag = 1` when used in the context of `ft_send_char()` and `ft_send_int()`, so that for each bit sent the `client` waits for a confirmation signal from the `server` before proceeding to send the data.\n\n___\n### `ft_sigaction.c`\n\nThis file contains only a wrapper for `sigaction` used to set both the `server`'s event handler and the `client`'s event handlers for `SIGUSR1` and `SIGUSR2` signals:\n\n\u003e Once again, error handling is done using control expressions inside `if` statements.\n```c\nvoid\tft_set_sigaction(struct sigaction *sa)\n{\n\tif (sigaction(SIGUSR1, sa, NULL) \u003c 0)\n\t\tft_perror_exit(\"sigaction() failed to handle SIGUSR1\");\n\tif (sigaction(SIGUSR2, sa, NULL) \u003c 0)\n\t\tft_perror_exit(\"sigaction() failed to handle SIGUSR2\");\n}\n```\n___\n\n## Usage 🏁\n\nTo try and test `minitalk`: \n\n* First clone the repository:\n```bash\ngit clone git@github.com:PedroZappa/42_minitalk.git\n```\n* Then fetch the project's dependencies and compile the executables:\n```bash\ncd 42_minitalk\nmake\n```\n* Get the `server` spinning:\n```bash\n./server\n```\n* Now, on a different terminal, run the `client`:\n```bash\n./client [server-pid] [message]\n```\n* The `client` will send the passed `message` to the target `server` with given `pid`.\n\n___\n## Testing 🧪\n\nIf you're like me and use `tmux` you can quickly test the project using the following make rules:\n\n* Conveniently spin up a `server` on a new `tmux` window-split:\n```sh\nmake serve\n```\n* To automatically launch a few `client`s on new `tmux` window-splits:\n```sh\nmake test\n```\n* To run harder tests with longer messages including Unicode characters:\n```sh\nmake stress_test\n```\n___\n## Appendix 📖\n\n### `Unicode` Character Encoding\n\n`Unicode`, like other character encodings, functions as a **lookup table** mapping **code points** to characters.\n\nThe most important difference between `Unicode` and `ASCII` is that `Unicode` allows character encodings to be up to 32-bits wide, allowing for over 4 billion unique values (way too much space than we'll ever need to include every character set in existence).\n\n___\n#### Variable Length Encoding\n\n`Unicode` takes a smart approach when it comes to character encoding. If a character can be represented by just 1 byte that's all the space that will be used. This memory efficient technique is known as **variable length encoding**.\n* For example a common character like a `C` takes 8 bits in memory, while special, rarer characters like `💩` need up to 32 bytes to be stored in memory.\n* This means a document like the present README takes about four times less space when encoded in UTF-8 than it would if encoded in UTF-32, making the page take less space in memory and load substantially faster.\n\n___\n#### Code Points\n\n`Unicode` characters can be referenced by their **code point**.\n\n* A **code point** is a (irreducible) **atomic unit of information**.\n* A text document is a sequence of **code points**. \n* Each **code point** represents a number with a particular meaning in the `Unicode` standard.\n* The current `Unicode` standard defines 1,114,112 **code points**.\n* These **code points** are further divided into **17 planes or groundings**.\n* Each **plane** is identified by a number from 0 to 16.\n* The number of **code points** in each plane is 65,536 ($2^{16}$).\n\nTo access a given **code point** we use the following syntax:\n\n* `U+(hexadecimal representation of a code point)`\n\n\u003e [!Note]\n\u003e Hexadecimal values are used to represent the **code points** because they make it easier to reference large values.\n\n| Character | Code Point | Binary Representation |\n| --------- | ---------- | --------------------- |\n| 💩        | U+1F4A9    | 0001 1111 0100 1010 1001 |\n| 🌟        | U+1F31F    | 0001 1111 0011 0001 1111 |\n\n___\n#### Grapheme Clusters\n\nSome characters can be expressed as a combination of multiple **code points** known as `grapheme clusters`.\n\n| Character | Code Point |\n| --------- | ---------- |\n| 🧑        | U+1F9D1    |\n| 🌾        | U+1F33E    |\n| 🧑‍🌾| U+1F9D1 U+200D U+1F33E |\n\n\u003e [!Important]\n\u003e `U+200D` is a **zero-width joiner**.\n\n___\n## License\n\nThis work is published under the terms of \u003ca href=\"https://github.com/PedroZappa/42_minitalk/blob/main/LICENSE\"\u003e42 Unlicense\u003c/a\u003e.\n\n\u003cp align=\"right\"\u003e(\u003ca href=\"#readme-top\"\u003eget to top\u003c/a\u003e)\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpedrozappa%2F42_minitalk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpedrozappa%2F42_minitalk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpedrozappa%2F42_minitalk/lists"}