基于信号的C语言IPC程序稳定性问题求助
问题
用C语言实现基于SIGUSR1/SIGUSR2的进程间通信时遇到稳定性问题:
- 编译运行
server.c可正常打印PID; - 编译运行
client.c传入服务端PID及100字符字符串时,使用usleep(3000)能得到预期输出,但usleep(100)会出现未定义行为(如字符错乱、丢失)。
server.c 代码
void handle_signal(int signo, siginfo_t *info, void *context) { static unsigned char ch = 0; static int idx = 0; (void)context; if (signo == SIGUSR1) ch <<= 1; else if (signo == SIGUSR2) ch = (ch << 1) | 1; idx++; if (idx == 8) { kill(info->si_pid, SIGUSR1); write(1, &ch, 1); idx = 0; } } int main(){ struct sigaction sa; printf("Server PID: %d\n", getpid()); sa.sa_handler = (void (*)(int))handle_signal; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; sigaction(SIGUSR1, &sa, NULL); sigaction(SIGUSR2, &sa, NULL); while (1) { pause(); } return 0; }
client.c 代码
void handle_input(int signo){ static int number = 0; if (signo == SIGUSR1) printf("Received letter %d!\n", ++number); else number = 0; } void handle_output(char **input, pid_t pid){ int i = 2; int j = 0; int bits = 8; unsigned char send; while (input[i]) { while (input[i][j] != '\0') { while (bits-- > 0) { send = input[i][j] >> bits & 1; if (send == 0) kill(pid, SIGUSR1); if(send == 1) kill(pid, SIGUSR2); usleep(100); } bits = 8; j++; } j = 0; i++; } } int main(int argc, char **argv){ char *ptr; struct sigaction sa; if (argc < 3 || !argv[2][0]) return (printf("Not enough values!\n")); ptr = argv[1]; while (isdigit(*ptr)) ptr++; if (*ptr != '\0') return (printf("Wrong pid format!\n")); sa.sa_handler = handle_input; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGUSR1, &sa, NULL); handle_output(argv, (pid_t)atoi(argv[1])); return (0); }
解决方案
核心问题是信号的不可靠性:内核不会对相同信号排队,当发送速度过快时,后续信号会被丢弃或合并,导致服务端接收的比特序列错乱。同时原代码存在信号处理函数类型不匹配、无同步机制等问题,需补充以下控制逻辑:
1. 实现字节级ACK确认机制
客户端发送完一个字节的8个比特后,必须等待服务端的确认信号(SIGUSR1),再发送下一个字节,彻底替代固定延时的不可靠同步:
- 在
client.c中添加全局volatile变量标记ACK状态(防止编译器优化):volatile int ack_received = 0; - 修改客户端信号处理函数,收到ACK时设置标记:
void handle_input(int signo){ static int number = 0; if (signo == SIGUSR1) { ack_received = 1; printf("Received letter %d!\n", ++number); } else { number = 0; } } - 修改
handle_output,发送完一个字节后阻塞等待ACK:void handle_output(char **input, pid_t pid){ int i = 2; int j = 0; int bits; unsigned char send; while (input[i]) { while (input[i][j] != '\0') { bits = 8; while (bits-- > 0) { send = input[i][j] >> bits & 1; // 检查kill调用是否成功 if (send == 0) { if (kill(pid, SIGUSR1) == -1) { perror("kill SIGUSR1 failed"); exit(EXIT_FAILURE); } } else { if (kill(pid, SIGUSR2) == -1) { perror("kill SIGUSR2 failed"); exit(EXIT_FAILURE); } } } // 等待服务端ACK ack_received = 0; while (!ack_received) { pause(); } j++; } j = 0; i++; } }
2. 修正服务端信号处理函数的类型匹配
原服务端错误地将sa_handler指向带3个参数的处理函数(设置了SA_SIGINFO标志时,应使用sa_sigaction),导致参数调用混乱,修正如下:
int main(){ struct sigaction sa; printf("Server PID: %d\n", getpid()); // 替换sa_handler为sa_sigaction,匹配SA_SIGINFO的函数签名 sa.sa_sigaction = handle_signal; sigemptyset(&sa.sa_mask); // 处理信号时阻塞SIGUSR1/SIGUSR2,避免并发修改静态变量 sigaddset(&sa.sa_mask, SIGUSR1); sigaddset(&sa.sa_mask, SIGUSR2); sa.sa_flags = SA_SIGINFO; sigaction(SIGUSR1, &sa, NULL); sigaction(SIGUSR2, &sa, NULL); while (1) { pause(); } return 0; }
3. 阻塞信号避免并发冲突
在服务端的信号掩码中添加SIGUSR1和SIGUSR2,确保处理一个信号时不会被另一个信号打断,防止静态变量ch和idx被并发修改(已在上一步的代码中实现)。
4. 添加信号发送错误检查
客户端调用kill时检查返回值,确保信号成功发送到服务端,避免因PID无效或权限问题导致的静默失败(已在handle_output的修改中实现)。
经过以上修改,程序不再依赖固定延时,通过ACK机制实现可靠的信号同步,可在任意输入长度下稳定运行。
内容的提问来源于stack exchange,提问作者alender
相关产品推荐
相关产品推荐

