hero-image

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: client and server.
  • 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_t length of the string so the server can malloc the exact amount of memory needed.
  • Concurrency Protection: If a second client tries to message the server while it’s busy, the server sends a SIGUSR1 back 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

  1. Start the server:

    ./server

    The server will output its PID (e.g., 12345).

  2. Send a message from the client:

    ./client 12345 "Hello, this is a long string sent via signals!"

Skills Demonstrated

  • Signal Handling: Professional use of sigaction and 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_calloc to manage strings of arbitrary length without wasting RAM.

Author

arri