Search code examples
csignals

Some signals are not getting caught by sigaction C


I'm trying to write a program that simulates communication between a server and a client using two processes, the server, and the client:

We will first start the server then the client (with arguments ./client [SERVER PID] [Message]), I'm allowed to send string Message to the server using just UNIX signals, so I'm using two signals SIGUSR1 and SIGUSR2 to send the value of the characters in binary, and then convert it back to char in the server and display it: so here's my client's code:

#include <minitalk_client.h>

void    send_message_to_server(pid_t ppid, const unsigned char *msg)
{
    unsigned char   mask;
    int             i;

    ft_putstr("Sending message `");
    ft_putstr((const char *)msg);
    ft_putstr("` to server: ");
    ft_putnbr(ppid);
    ft_putchar('\n');
    while (*msg)
    {
        mask = 1 << 7;
        while (mask)
        {
            if (*msg & mask)
                kill(ppid, SIGUSR1);
            else
                kill(ppid, SIGUSR2);
            usleep(300);
            mask >>= 1;
        }
        msg++;
    }
    i = -1;
    while (++i < 8)
    {
        kill(ppid, SIGUSR2);
        usleep(300);
    }
}

int main(int argc, char **argv)
{
    int ppid;

    if (argc != 3)
    {
        write(2, "Invalid arguments\n", 18);
        return (1);
    }
    ppid = ft_atoi(argv[1]);
    send_message_to_server(ppid, (const unsigned char *)argv[2]);
    return (0);
}

and the server:

#include <minitalk_server.h>

void    sig_handler(int sig)
{
    static unsigned char    character;
    static unsigned char    mask = 1 << 7;
    static bool             is_new_message = true;

    if (is_new_message)
    {
        ft_putstr("Received message from client: ");
        is_new_message = false;
    }
    if (sig == SIGUSR1)
        character |= mask;
    mask >>= 1;
    if (!mask)
    {
        if (character)
            ft_putchar(character);
        else
        {
            ft_putchar('\n');
            is_new_message = true;
        }
        character = 0;
        mask = 1 << 7;
    }
}

int main(void)
{
    struct sigaction    act;
    const pid_t         pid = getpid();

    sigemptyset(&act.sa_mask);
    act.sa_handler = sig_handler;

    sigaction(SIGUSR1, &act, 0);
    sigaction(SIGUSR2, &act, 0);
    ft_putstr("Started minitalk server with pid: ");
    ft_putnbr(pid);
    ft_putchar('\n');
    while (1)
        pause();
    return (0);
}

So my code works but I have a problem with sleep when I don't wait enough time some signals don't get caught and it does things like this:

Suspendisse consectetur consequaa�@������\@����@����@��������@�����\@��������@������@����@��@���������@������\@�����@���@������@���������X

and I don't want to wait too much because that makes my program too slow, so is there a way to put signals in "queue" so that when I want to send multiple signals in a loop, there is no need to wait between each one?

(I know that signals are not the best method to do that but it's a school project and I'm only allowed to do that)


Solution

  • Since

    I don't want to wait too much because that makes my program too slow

    , it makes sense to ask

    is there a way to put signals in "queue" so that when I want to send multiple signals in a loop, there is no need to wait between each one?

    . But in fact no, SIGUSR1, SIGUSR2, and the other ordinary signals do not queue. Thus, not only can you have at most one of each pending at a time, but if you do have both a SIGUSR1 and a SIGUSR2 pending at the same time, then the order in which they are delivered is unspecified, and may differ from the order in which the client raised them.

    But there may be another alternative for speeding the interaction and making it more reliable, while still relying only on signals for communication. By setting the SA_SIGINFO flag when you register the handler, you can use a handler that accepts three arguments, with the second being a pointer to a siginfo_t, a structure containing information about the signal. That information includes the PID of the process that sent it. You could use that to enable the server to acknowledge receipt of each signal by sending a signal back to the client. The client would then wait for acknowledgement of each signal before sending the next.

    The server would do something along these lines:

    void sig_handler(int sig, siginfo_t *info, void *ignore) {
        // ...
    
        kill(info->si_pid, SIGUSR1);
    }
    
    int main(void) {
        struct sigaction act = { .sa_sigaction = sig_handler, .flags = SA_SIGINFO };
    
        sigemptyset(&act.sa_mask);
        sigaction(SIGUSR1, &act, 0);
        sigaction(SIGUSR2, &act, 0);
    
        // ...
    }
    

    If you want to pursue this then I leave the client-side modifications to you, but I do suggest that you consider that side (at least) receiving the signals synchronously via sigwait() instead of asynchronously via a signal handler.