Minitalk a 42 Project

Minitalk a 42 Project

Creating a mini communication programs

·

5 min read

At 42, students tackle projects that teach coding skills in creative ways. Minitalk is one such project. It's all about using UNIX signals to make two programs—a client and a server—that can talk to each other. The challenge? The client has to send messages with all kinds of characters, like emojis and Arabic letters, to the server. And when the server gets a message, it has to print it out.

In this article, we'll break down Minitalk in easy terms. From how it works with UNIX signals to how it lets you send diverse messages between the client and server, Minitalk is a hands-on project that helps students understand coding in a practical way. Let's dive into Minitalk and see how it's making communication simpler and more fun at 42.

What are Unix signals?

Based on Wikipedia's definition: " Signals are standardized messages sent to a running program to trigger specific behaviour, such as quitting or error handling. They are a limited form of inter-process communication (IPC), typically used in Unix, Unix-like, and other POSIX-compliant operating systems".

How did I solve the Task and prevent garbage values?

To tackle the issue of garbage values caused by signal loss, I came up with a solution: sending the length of the message in the first 32 signals. This way, the server knows how long the message will be and can allocate memory accordingly. By doing this, I ensure that even if some signals are lost, the server still waits for the complete message before processing it. This approach prevents incomplete or corrupted data from messing up the communication process, making it more reliable overall.

// sending one byte
static void    _send_char(int pid, unsigned char c)
{
    int    i;

    i = 128;
    while (i > 0)
    {
        if (c >= i)
        {
            kill(pid, SIGUSR2);
            c -= i;
        }
        else
        {
            kill(pid, SIGUSR1);
        }
        i /= 2;
        usleep(200);
    }
}

// sending the lenght
static void    _sendlen(int pid, int len)
{
    char    *_len;
    int        i;

    i = 3;
    _len = (char *)&len;
    while (i >= 0)
    {
        _send_char(pid, _len[i]);
        i--;
    }
}

How do I collect the Message on the server side?

Collecting the Message on the Server Side

Once the server has allocated memory for the incoming message based on the received length, it begins the process of collecting the message signals. This crucial step ensures that the server can reconstruct the complete message from the individual signals it receives. The server accomplishes this task through a combination of signal handling and data processing functions. Let's break down how it works:

Signal Handling:
The sig_handler function is responsible for managing incoming signals. It first determines the sender's ID and resets values if the sender changes. Then, it handles the allocation of memory for the message length and processes the incoming message data signals.
Processing Message Data:
The process_msg_data function is called when the server receives signals representing the message data. Using bit-shifting techniques, this function reconstructs the individual bytes of the message. Each byte is then appended to the message buffer, gradually building the complete message.
Completion Check:
Throughout this process, the server keeps track of the number of signals received (g_data.id). Once it has received all the signals necessary to reconstruct the entire message (including both the length and the message data), it proceeds to print out the complete message. After printing, it resets the values to prepare for the next incoming message.
In essence, this method ensures that even in the face of signal loss or interruption, the server can reliably collect and reconstruct the incoming message, safeguarding the integrity of the communication process. Through effective signal handling and data processing, the server demonstrates its ability to efficiently handle and process incoming messages, making it a crucial component of the Minitalk communication protocol.

// allocation
void    alloc_handler(int nbr)
{
    if (nbr <= 0)
        ft_reset_val(&g_data);
    g_data.msg = ft_calloc((g_data.len) + 1, 1);
    if (!g_data.msg)
        ft_reset_val(&g_data);
}

static void    process_msg_data(int signum)
{
    if (g_data.c_index < 8)
    {
        if (signum == SIGUSR1)
            g_data.c = (g_data.c << 1);
        else
            g_data.c = (g_data.c << 1) | 1;
        g_data.c_index++;
    }
    if (g_data.c_index == 8)
    {
        g_data.msg[g_data.m_index++] = g_data.c;
        g_data.c = 0;
        g_data.c_index = 0;
    }
}

// signal handler
void    sig_handler(int signum, siginfo_t *info, void *con)
{
    (void) con;
    if (g_data.sender != 0 && (g_data.sender != info->si_pid))
        ft_reset_val(&g_data);
    else if (g_data.sender == 0)
        g_data.sender = info->si_pid;
    if (g_data.id == 32)
        alloc_handler(g_data.len);
    if (g_data.id < 32 && (signum == SIGUSR1 || signum == SIGUSR2))
    {
        if (signum == SIGUSR1)
            g_data.len = (g_data.len << 1);
        else
            g_data.len = (g_data.len << 1) | 1;
    }
    else if (g_data.id >= 32 && (signum == SIGUSR1 || signum == SIGUSR2))
        process_msg_data(signum);
    g_data.id++;
    if (g_data.id == ((g_data.len * 8) + 32))
    {
        mini_printf(1, "%s\n", g_data.msg);
        ft_reset_val(&g_data);
    }
}

What if another client is sending at the same time ?

Handling multiple clients simultaneously in a signal-based communication system poses a unique challenge. To address this, your solution involves tracking the sender's process ID (PID) and resetting memory allocation and variables when a new PID is detected. This approach ensures that each client's message is processed independently, preventing interference between concurrent communications.

When another client begins sending messages, the server checks if the sender's PID has changed. If it has, indicating a new client, the server frees the memory allocated for the previous client's message and resets all relevant variables. This ensures a clean slate for processing the new client's message without any residual data from previous communications.

By dynamically managing memory and variables based on the sender's PID, your solution effectively handles multiple clients concurrently, ensuring robust and reliable communication in a multi-client environment. This proactive approach to client management enhances the scalability and efficiency of the Minitalk communication protocol, enabling seamless communication between multiple clients and the server.

// reset function
void    ft_reset_val(t_data *data)
{
    free(data->msg);
    data->sender = 0;
    data->msg = NULL;
    data->id = 0;
    data->c = 0;
    data->c_index = 0;
    data->len = 0;
    data->m_index = 0;
}