Minitalk
Minitalk
A high-speed, reliable data transmission system between two processes using UNIX signals. This implementation features a synchronization protocol to ensure no bits are lost during transmission.
Project Requirements
- Executables:
clientandserver. - Makefile: Compiles without relinking.
- Error Handling: Robust handling of PIDs and invalid arguments; zero crashes.
- Memory Management: 100% leak-free (dynamic allocation based on string length).
- Global Variables: Strictly one per program, used to manage asynchronous signal states.
Features
- Bidirectional Communication: The client waits for an acknowledgment signal (
SIGUSR2) from the server before sending the next bit. - Dynamic Allocation: The client first transmits the
size_tlength of the string so the server canmallocthe exact amount of memory needed. - Concurrency Protection: If a second client tries to message the server while it’s busy, the server sends a
SIGUSR1back to the new client, triggering a “Server Busy!” exit.
Implementation Details
1. Bit-by-Bit Protocol (Client)
The client sends the string length first, then the string itself. It uses a volatile sig_atomic_t global variable to “pause” until the server confirms receipt of the current bit.
static void send_bit(int pid, int bit)
{
g_client = 0;
if (bit)
kill(pid, SIGUSR1);
else
kill(pid, SIGUSR2);
// Wait for the server's acknowledgment signal
while (!g_client)
usleep(100);
}
2. State Management (Server)
The server uses a global struct to track the current client PID, the length of the string being received, and the bit index. It uses SA_SIGINFO to identify which process is sending the signal.
static void handler(int signal, siginfo_t *info, void *ctx)
{
(void)ctx;
// Check if the server is already occupied by another client
if (g_server.client_pid != 0 && g_server.client_pid != info->si_pid)
{
kill(info->si_pid, SIGUSR1);
return ;
}
g_server.signal = signal;
g_server.client_pid = info->si_pid;
g_server.new_signal = 1;
}
3. Data Reconstruction
Once the server receives the full size_t length, it allocates memory and begins filling the buffer:
static void recived_char(void)
{
if (g_server.signal == SIGUSR1)
g_server.curr_char |= (1 << g_server.bit_i);
g_server.bit_i++;
if (g_server.bit_i == 8)
{
g_server.str[g_server.str_i++] = (char) g_server.curr_char;
g_server.bit_i = 0;
if (g_server.curr_char == 0)
{
// End of string reached: print and reset state
ft_putstr_fd(g_server.str, 1);
// ... (free and reset logic)
}
}
kill(g_server.client_pid, SIGUSR2); // Send ACK
}
Usage
Compilation
make
Running the System
-
Start the server:
./serverThe server will output its PID (e.g.,
12345). -
Send a message from the client:
./client 12345 "Hello, this is a long string sent via signals!"
Skills Demonstrated
- Signal Handling: Professional use of
sigactionand signal masks to prevent race conditions. - Binary Manipulation: Mastering bitwise operators (
<<,>>,&,|) for data encoding. - Asynchronous Logic: Designing a handshake system to synchronize two independent processes.
- Memory Efficiency: Using
ft_callocto manage strings of arbitrary length without wasting RAM.
Author
- GitHub: cuteKittenArri