MST

星途 面试题库

面试题:C语言匿名管道通信中的同步与错误处理

在C语言匿名管道通信中,如果出现读端和写端的同步问题,你会如何解决?另外,列举一些可能出现的错误,并说明如何在代码中进行有效的错误处理。
11.0万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

解决读端和写端同步问题的方法

  1. 使用信号
    • 写端在写完数据后发送一个信号给读端,读端捕获该信号后进行读操作。例如使用sigaction函数来设置信号处理函数,kill函数来发送信号。
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    #include <string.h>
    
    int pipefd[2];
    void signal_handler(int signum) {
        char buffer[1024];
        ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer));
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("Read from pipe: %s\n", buffer);
        }
    }
    
    int main() {
        if (pipe(pipefd) == -1) {
            perror("pipe");
            exit(EXIT_FAILURE);
        }
    
        struct sigaction sa;
        sa.sa_handler = signal_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        if (sigaction(SIGUSR1, &sa, NULL) == -1) {
            perror("sigaction");
            exit(EXIT_FAILURE);
        }
    
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork");
            exit(EXIT_FAILURE);
        } else if (pid == 0) { // 子进程(写端)
            close(pipefd[0]);
            const char *message = "Hello, pipe!";
            if (write(pipefd[1], message, strlen(message)) != strlen(message)) {
                perror("write");
                exit(EXIT_FAILURE);
            }
            close(pipefd[1]);
            kill(getppid(), SIGUSR1);
            exit(EXIT_SUCCESS);
        } else { // 父进程(读端)
            close(pipefd[1]);
            pause();
            close(pipefd[0]);
            wait(NULL);
            exit(EXIT_SUCCESS);
        }
    }
    
  2. 使用文件描述符的阻塞与非阻塞模式
    • 将读端或写端设置为非阻塞模式。例如使用fcntl函数设置文件描述符为非阻塞。写端在非阻塞模式下,如果管道已满,write函数会立即返回错误,此时写端可以选择等待一段时间后重试。读端在非阻塞模式下,如果管道中无数据,read函数也会立即返回错误,读端可以等待或执行其他任务。
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <string.h>
    
    int main() {
        int pipefd[2];
        if (pipe(pipefd) == -1) {
            perror("pipe");
            exit(EXIT_FAILURE);
        }
    
        // 设置读端为非阻塞
        int flags = fcntl(pipefd[0], F_GETFL, 0);
        if (flags == -1) {
            perror("fcntl F_GETFL");
            exit(EXIT_FAILURE);
        }
        if (fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK) == -1) {
            perror("fcntl F_SETFL");
            exit(EXIT_FAILURE);
        }
    
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork");
            exit(EXIT_FAILURE);
        } else if (pid == 0) { // 子进程(写端)
            close(pipefd[0]);
            const char *message = "Hello, pipe!";
            ssize_t bytes_written = 0;
            while (bytes_written < strlen(message)) {
                ssize_t res = write(pipefd[1], message + bytes_written, strlen(message) - bytes_written);
                if (res == -1) {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        // 管道满,等待一段时间
                        usleep(100000);
                    } else {
                        perror("write");
                        exit(EXIT_FAILURE);
                    }
                } else {
                    bytes_written += res;
                }
            }
            close(pipefd[1]);
            exit(EXIT_SUCCESS);
        } else { // 父进程(读端)
            close(pipefd[1]);
            char buffer[1024];
            ssize_t bytes_read;
            while ((bytes_read = read(pipefd[0], buffer, sizeof(buffer))) == -1) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    // 无数据,等待一段时间
                    usleep(100000);
                } else {
                    perror("read");
                    exit(EXIT_FAILURE);
                }
            }
            buffer[bytes_read] = '\0';
            printf("Read from pipe: %s\n", buffer);
            close(pipefd[0]);
            wait(NULL);
            exit(EXIT_SUCCESS);
        }
    }
    

可能出现的错误及错误处理

  1. pipe函数失败
    • 错误原因:系统资源不足,例如打开的文件描述符过多。
    • 错误处理:在调用pipe函数后,通过检查返回值来判断是否成功。如果pipe返回-1,使用perror函数打印错误信息并根据情况退出程序。
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    
  2. fork函数失败
    • 错误原因:系统资源不足,例如进程表已满。
    • 错误处理:在调用fork函数后,通过检查返回值来判断是否成功。如果fork返回-1,使用perror函数打印错误信息并根据情况退出程序。
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    
  3. read函数失败
    • 错误原因:读端文件描述符无效、管道被关闭、读操作被信号中断等。
    • 错误处理:在调用read函数后,检查返回值。如果返回-1,根据errno的值判断错误类型。例如,如果errnoEINTR,表示读操作被信号中断,可以重新调用read;如果是其他错误,使用perror打印错误信息并根据情况处理,如关闭文件描述符并退出程序。
    char buffer[1024];
    ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer));
    if (bytes_read == -1) {
        if (errno == EINTR) {
            // 重新调用read
            bytes_read = read(pipefd[0], buffer, sizeof(buffer));
        } else {
            perror("read");
            // 关闭文件描述符并退出程序等处理
            close(pipefd[0]);
            exit(EXIT_FAILURE);
        }
    }
    
  4. write函数失败
    • 错误原因:写端文件描述符无效、管道已满(非阻塞模式下)、写操作被信号中断等。
    • 错误处理:在调用write函数后,检查返回值。如果返回-1,根据errno的值判断错误类型。例如,如果errnoEINTR,可以重新调用write;如果是EAGAINEWOULDBLOCK(非阻塞模式下管道满),可以等待一段时间后重试;对于其他错误,使用perror打印错误信息并进行相应处理,如关闭文件描述符并退出程序。
    const char *message = "Hello, pipe!";
    ssize_t bytes_written = write(pipefd[1], message, strlen(message));
    if (bytes_written == -1) {
        if (errno == EINTR) {
            // 重新调用write
            bytes_written = write(pipefd[1], message, strlen(message));
        } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
            // 等待一段时间后重试
            usleep(100000);
            bytes_written = write(pipefd[1], message, strlen(message));
        } else {
            perror("write");
            // 关闭文件描述符并退出程序等处理
            close(pipefd[1]);
            exit(EXIT_FAILURE);
        }
    }