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
相关产品推荐
相关产品推荐

