Skip to main content

The Core Concept

Minitalk transmits text by breaking each character into its 8 binary bits and sending them one at a time. Each bit is represented by a different signal:
  • Bit 0 → Send SIGUSR1
  • Bit 1 → Send SIGUSR2
The server receives these signals and reconstructs the original character by assembling the bits back together.

Character to Binary Conversion

Let’s see how the character 'A' is transmitted:
Character: 'A'
ASCII value: 65
Binary: 01000001

Transmission order (MSB first):
0 → SIGUSR1
1 → SIGUSR2
0 → SIGUSR1
0 → SIGUSR1
0 → SIGUSR1
0 → SIGUSR1
0 → SIGUSR1
1 → SIGUSR2
Minitalk transmits bits in MSB (Most Significant Bit) first order, meaning the leftmost bit is sent first. This is the standard for network protocols.

Client: Bit Extraction and Transmission

The send_message function in client.c:42-68 handles the bit-by-bit transmission:
int	send_message(unsigned int pid, unsigned char *str)
{
	int	bits;
	int	i;

	i = 0;
	while (str[i])
	{
		bits = 8;
		while (--bits >= 0)
		{
			if ((str[i] >> bits) & 1)
			{
				if (kill(pid, SIGUSR2) == -1)
					return (1);
			}
			else
			{
				if (kill(pid, SIGUSR1) == -1)
					return (1);
			}
			usleep(700);
		}
		i++;
	}
	return (0);
}

Understanding the Bit Extraction

The key line is client.c:53:
if ((str[i] >> bits) & 1)
Let’s break down how this extracts individual bits:
Starting value: str[i] = 65 (binary: 01000001)Iteration 1 (bits = 7):
65 >> 7 = 0b01000001 >> 7 = 0b00000000 = 0
0 & 1 = 0  →  Send SIGUSR1
Iteration 2 (bits = 6):
65 >> 6 = 0b01000001 >> 6 = 0b00000001 = 1
1 & 1 = 1  →  Send SIGUSR2
Iteration 3 (bits = 5):
65 >> 5 = 0b01000001 >> 5 = 0b00000010 = 2
2 & 1 = 0  →  Send SIGUSR1
And so on for all 8 bits…
Right shift operator (>>): Shifts all bits to the right by N positionsBitwise AND (&): Compares bits; & 1 isolates the rightmost bitCombined, (value >> n) & 1 extracts the bit at position n from the right.

Server: Bit Reconstruction

The server’s handle_sigusr function (server.c:16-32) reconstructs characters from incoming bits:
void	handle_sigusr(int sigsent)
{
	static size_t	c = 0;
	static size_t	i = 0;

	if (sigsent == SIGUSR1)
		c = c << 1;
	else if (sigsent == SIGUSR2)
		c = (c << 1) | 1;
	i++;
	if (i == 8)
	{
		ft_printf("%c", c);
		i = 0;
		c = 0;
	}
}

Understanding Bit Reconstruction

Two key operations happen in server.c:21-24: For SIGUSR1 (bit 0):
c = c << 1;
Shifts left and adds a 0 in the rightmost position. For SIGUSR2 (bit 1):
c = (c << 1) | 1;
Shifts left and sets the rightmost bit to 1 using bitwise OR.
Goal: Receive 01000001 and reconstruct the value 65Initial state: c = 0b00000000, i = 0Signal 1: SIGUSR1 (bit 0)
c = 0b00000000 << 1 = 0b00000000
i = 1
Signal 2: SIGUSR2 (bit 1)
c = (0b00000000 << 1) | 1 = 0b00000001
i = 2
Signal 3: SIGUSR1 (bit 0)
c = 0b00000001 << 1 = 0b00000010
i = 3
Signal 4-7: SIGUSR1 (bits 0, 0, 0, 0)
c = 0b00000010 << 1 = 0b00000100
c = 0b00000100 << 1 = 0b00001000
c = 0b00001000 << 1 = 0b00010000
c = 0b00010000 << 1 = 0b00100000
i = 7
Signal 8: SIGUSR1 (bit 0)
c = 0b00100000 << 1 = 0b01000000
i = 8
Signal 9: SIGUSR2 (bit 1)
c = (0b01000000 << 1) | 1 = 0b01000001 = 65
i = 8  →  Print 'A' and reset

Timing: The usleep(700) Mystery

In client.c:63, there’s a critical delay:
usleep(700);
Why is this necessary?Without the delay, the client would send signals faster than the server can process them. UNIX signals are not queued - if multiple identical signals arrive while one is being handled, extras are dropped.The 700 microsecond delay ensures:
  1. The previous signal handler finishes execution
  2. The server returns to the pause() state
  3. The server is ready to receive the next signal

Timing Considerations

// Too fast - signals will be lost
usleep(10);  // ❌

// Good - reliable transmission
usleep(700); // ✅

// Too slow - unnecessary waiting
usleep(10000); // ⏱️
The value 700 microseconds is a balance between:
  • Reliability: High enough to prevent signal loss
  • Performance: Low enough to maintain reasonable speed
Did you know? The optimal delay can vary by system. On faster systems with lower latency, you might get away with usleep(500). On slower or heavily loaded systems, you might need usleep(1000) or more.

Complete Transmission Example

Let’s trace the full transmission of the string "Hi":

Character ‘H’ (ASCII 72 = 0b01001000)

Bit 7 (0): SIGUSR1 → c = 0b00000000
Bit 6 (1): SIGUSR2 → c = 0b00000001
Bit 5 (0): SIGUSR1 → c = 0b00000010
Bit 4 (0): SIGUSR1 → c = 0b00000100
Bit 3 (1): SIGUSR2 → c = 0b00001001
Bit 2 (0): SIGUSR1 → c = 0b00010010
Bit 1 (0): SIGUSR1 → c = 0b00100100
Bit 0 (0): SIGUSR1 → c = 0b01001000 = 72 → Print 'H'

Character ‘i’ (ASCII 105 = 0b01101001)

Bit 7 (0): SIGUSR1 → c = 0b00000000
Bit 6 (1): SIGUSR2 → c = 0b00000001
Bit 5 (1): SIGUSR2 → c = 0b00000011
Bit 4 (0): SIGUSR1 → c = 0b00000110
Bit 3 (1): SIGUSR2 → c = 0b00001101
Bit 2 (0): SIGUSR1 → c = 0b00011010
Bit 1 (0): SIGUSR1 → c = 0b00110100
Bit 0 (1): SIGUSR2 → c = 0b01101001 = 105 → Print 'i'
Result: The server prints "Hi"

Key Takeaways

  1. Bit extraction: (value >> position) & 1 isolates individual bits
  2. Bit reconstruction: (accumulator << 1) | bit builds the character
  3. MSB first: Transmitting from bit 7 to bit 0 is standard
  4. Timing matters: usleep(700) prevents signal loss
  5. 8 bits = 1 byte: Each character requires exactly 8 signal transmissions
This binary transmission method can send any 8-bit data, not just printable ASCII characters. You could transmit binary files, Unicode (UTF-8 encoded), or any other byte stream using the same technique.

Build docs developers (and LLMs) love