You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言System Signals客户端服务器通信:客户端未处理信号崩溃排查

问题描述

我正在开发一个客户端-服务器通信项目,客户端将char *字符串编码为比特位后发送给服务器:用SIGUSR1发送0,SIGUSR2发送1。服务器每收到一个信号就返回SIGUSR1作为确认;收到终止符\0后,服务器打印消息并向客户端发送SIGUSR2。

但运行时偶尔会出现客户端未处理信号的情况,终端报错:

zsh: user-defined signal 1  ./client 46339 test

此时信号未被处理,进程直接中断,项目停止运行。我尝试增加客户端和服务器侧usleep()的时长,但未能解决该问题。每次触发bug时,服务器端的buffer仍有残留内容,导致后续调用./client时无法正常工作,会打印不可见字符。

客户端代码
#include "minitalk.h"

volatile sig_atomic_t   g_ack_received = 0;

/*Waits for a acknowledgment rom the server before processing the next signal.
If no acknowledgment has been received after one second, '\1' is sent to the
server so that server resources are properly freed and the server can receive a
new message*/
void    wait_for_server_ack(int pid, int delay)
{
    int timeout;
    int i;

    timeout = 0;
    while (!g_ack_received)
    {
        usleep(delay);
        if (++timeout > 10 * delay)
        {
            i = 0;
            while (i--)
            {
                if ('\1' >> i & 1)
                    send_signal(pid, SIGUSR2);
                else
                    send_signal(pid, SIGUSR1);
                usleep(delay);
            }
            exit(ft_printf_colour(RED_BOLD, TIME_OUT));
        }
    }
}

/*Checks that the arguments of the program are valid:
• Two arguments to the 'client' program
• First argument is numeric (PID of the 'server')
• First argument is not a protected process (PID < 1050)
• Second argument is a not empty string*/
t_bool  argument_is_valid(int argc, char **argv)
{
    const char  *error_msg = NULL;

    if (argc != 3)
        error_msg = ERR_ARG_NR;
    else if (!ft_isnumeric(argv[1]))
        error_msg = ERR_NON_NUM_PID;
    else if (ft_atoi(argv[1]) < 1050)
        error_msg = PROTECTED_PID;
    else if (!ft_strlen(argv[2]))
        error_msg = ERR_EMPT_STR;
    if (error_msg)
    {
        ft_printf_colour(RED_BOLD, error_msg);
        return (FALSE);
    }
    return (TRUE);
}

/*Sends a bit encoded message to the server whose PID is 'pid' with SIGUSR1 to
represent 0 and SIGUSR2 to represent 1
Once the message is sent, 11111111 (bit representation of the NULL terminator) is
sent to the server to indicate message is over*/
void    send_message(int pid, char *str)
{
    int     i;
    char    c;

    while (*str)
    {
        i = 8;
        c = *str++;
        while (i--)
        {
            g_ack_received = 0;
            if (c >> i & 1)
                send_signal(pid, SIGUSR2);
            else
                send_signal(pid, SIGUSR1);
            wait_for_server_ack(pid, 500);
        }
    }
    i = 8;
    while (i--)
    {
        g_ack_received = 0;
        send_signal(pid, SIGUSR1);
        wait_for_server_ack(pid, 500);
    }
}

/*Displays a message from the client side to assess that the server did
receive the message properly*/
void    handle_sigusr_client(int signum)
{
    static int  bit_count = 0;

    if (signum == SIGUSR1)
    {
        bit_count++;
        g_ack_received = 1;
    }
    if (signum == SIGUSR2)
        ft_printf_colour(GREEN_LIGHT,
            "Done, %d characters received by server", bit_count / 8);
}

/*Checks that the program arguments are valids and sends message to the server.
Expects a signal from the server once the message has been received*/
int main(int argc, char **argv)
{
    struct sigaction    sa;

    if (!argument_is_valid(argc, argv))
        return (1);
    sa.sa_handler = handle_sigusr_client;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGUSR1, &sa, NULL) == -1
        || sigaction(SIGUSR2, &sa, NULL) == -1)
    {
        ft_printf(RED_BOLD, ERR_SIGAC);
        return (1);
    }
    send_message(ft_atoi(argv[1]), argv[2]);
    return (0);
}
服务器代码
#include "minitalk.h"

/*Checks that the message buffer can be freed, frees it and sets it to NULL*/
int free_resources(char **message)
{
    if (*message)
    {
        free(*message);
        *message = NULL;
    }
    return (0);
}

/*Adds the character 'c' at the end of the string pointed by 'str'. If 'str' is
not big enough to receive the new character, memory is reallocated to increase
the capacity of 'str' by BLOCK_SIZE
add_char_to_str ensures that 'str' is NULL terminated*/
void    add_char_to_str(char c, char **str)
{
    static int  capacity = BLOCK_SIZE;
    static int  size = 0;
    char        *new_str;

    if (!(*str))
    {
        capacity = BLOCK_SIZE;
        size = 0;
        *str = (char *)malloc(capacity * sizeof(char));
        if (!(*str))
            exit(ft_printf_colour(RED_BOLD, "%s", ERR_MALLOC));
    }
    if (size + 2 > capacity)
    {
        capacity += BLOCK_SIZE;
        new_str = (char *)malloc(capacity * sizeof(char));
        if (!new_str)
            exit(ft_printf_colour(RED_BOLD, "%s", ERR_MALLOC));
        ft_memmove(new_str, *str, size);
        free(*str);
        *str = new_str;
    }
    (*str)[size] = c;
    (*str)[++size] = '\0';
}

/*Functions checks that only SIGUSR1 and SIGUSR2 are processed by the server.
It accumulates bits received by the client in a buffer int before storing each
byte in a static char * 'message'
Once a NULL terninator is received by the client, 'message' is displayed on the
standard output and memory is properly freed*/
void    handle_sigusr_server(int signum, siginfo_t *info, void *context)
{
    static int  buffer = 0;
    static int  bits_received = 0;
    static char *message = NULL;

    (void)context;
    if (signum == SIGINT)
        exit(free_resources(&message));
    buffer = (buffer << 1 | (signum == SIGUSR2));
    if (++bits_received == 8)
    {
        if ((char)buffer == '\1')
            free_resources(&message);
        else if ((char)buffer == '\0')
        {
            ft_printf("%s\n", message);
            free_resources(&message);
            send_signal(info->si_pid, SIGUSR2);
        }
        else
            add_char_to_str((char)buffer, &message);
        buffer = 0;
        bits_received = 0;
    }
    send_signal(info->si_pid, SIGUSR1);
}

/*Displays the PID of the server once it is launched and then waits for SIGUSR1
and SIGUSR2 from the client to display the encoded message*/
int main(void)
{
    struct sigaction    sa;

    sa.sa_handler = 0;
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = handle_sigusr_server;
    sigemptyset(&sa.sa_mask);
    ft_printf_colour(YELLOW_BOLD, "Server PID: %i\n\n", getpid());
    if (sigaction(SIGINT, &sa, NULL) == -1
        || sigaction(SIGUSR1, &sa, NULL) == -1
        || sigaction(SIGUSR2, &sa, NULL) == -1)
    {
        ft_printf(RED_BOLD, ERR_SIGAC);
        return (1);
    }
    while (1)
        pause();
}
公共代码
#include "minitalk.h"

/*Sends the signal 'signu;' to the process whith ID 'PID' and prints the errno
and exits the process in case of failure of kill() function*/
void    send_signal(pid_t pid, int signum)
{
    if (kill(pid, signum) == -1)
    {
        ft_printf_colour(RED_BOLD, KILL_FAIL, errno);
        exit (EXIT_FAILURE);
    }
}
问题根源与修复方案

1. 客户端超时逻辑致命bug

客户端wait_for_server_ack函数中,超时处理循环初始值i=0导致循环不执行,无法向服务器发送重置信号;同时超时计算逻辑错误,实际超时时间与预期不符。修复后:

void    wait_for_server_ack(int pid, int delay)
{
    int timeout;
    int i;

    timeout = 0;
    // 按1秒超时计算循环次数
    while (!g_ack_received)
    {
        usleep(delay);
        if (++timeout > 1000000 / delay)
        {
            // 发送\1的8位二进制
            i = 8;
            while (i--)
            {
                if ((0x01 >> i) & 1)
                    send_signal(pid, SIGUSR2);
                else
                    send_signal(pid, SIGUSR1);
                usleep(delay);
            }
            exit(ft_printf_colour(RED_BOLD, TIME_OUT));
        }
    }
}

2. 信号竞争与未屏蔽信号

客户端发送信号后,若服务器ACK在g_ack_received = 0和等待循环之间到达,会导致同步混乱;同时未屏蔽信号可能引发嵌套中断。修改客户端sigaction设置:

// 客户端main函数中
sa.sa_handler = handle_sigusr_client;
sigemptyset(&sa.sa_mask);
// 处理信号时屏蔽自身,避免嵌套
sigaddset(&sa.sa_mask, SIGUSR1);
sigaddset(&sa.sa_mask, SIGUSR2);
sa.sa_flags = SA_RESTART; // 重启被信号中断的系统调用

3. 服务器静态变量未重置

客户端异常断开时,服务器buffer和bits_received残留值会导致后续乱码,需在收到\1时重置:

// 服务器handle_sigusr_server函数中
if ((char)buffer == '\1')
{
    free_resources(&message);
    buffer = 0;
    bits_received = 0;
}

4. 服务器重复发送ACK

服务器收到\0后发送SIGUSR2通知完成,却又额外发送SIGUSR1,干扰客户端逻辑,需调整代码:

// 服务器handle_sigusr_server函数中
else if ((char)buffer == '\0')
{
    ft_printf("%s\n", message);
    free_resources(&message);
    send_signal(info->si_pid, SIGUSR2);
    buffer = 0;
    bits_received = 0;
    return; // 跳过后续ACK发送
}

5. 客户端完成信号处理逻辑

客户端收到SIGUSR2后需直接退出,避免继续执行后续代码:

void    handle_sigusr_client(int signum)
{
    static int  bit_count = 0;

    if (signum == SIGUSR1)
    {
        bit_count++;
        g_ack_received = 1;
    }
    if (signum == SIGUSR2)
    {
        ft_printf_colour(GREEN_LIGHT,
            "Done, %d characters received by server", bit_count / 8);
        exit(EXIT_SUCCESS);
    }
}

内容的提问来源于stack exchange,提问作者Théo ZANCHI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 07:57:33