无法修改子进程管道缓冲区大小:非阻塞读取tcpdump输出问题
问题描述
我希望运行tcpdump持续5秒并获取其stdout输出。问题在于,当我使用非阻塞模式读取时,必须等待缓冲区填满至4096字节才能获取到tcpdump的stdout内容。我已多次搜索并尝试多种方法,但仍未解决问题。请指出我的错误所在,以及如何在非阻塞模式下读取子进程的stdout,无需等待缓冲区填满?
附当前代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/wait.h> #include <string.h> #include <errno.h> #define PIPE_READ 0 #define PIPE_WRITE 1 // 将文件描述符设置为非阻塞模式 void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { perror("fcntl F_GETFL"); exit(1); } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { perror("fcntl F_SETFL"); exit(1); } } int main() { int stdout_pipe[2], stderr_pipe[2]; pid_t pid; // 创建两个管道:一个用于stdout,一个用于stderr if (pipe(stdout_pipe) == -1) { perror("pipe stdout"); exit(1); } if (pipe(stderr_pipe) == -1) { perror("pipe stderr"); exit(1); } // 分叉进程 pid = fork(); if (pid == -1) { // 分叉进程出错 perror("fork"); exit(1); } if (pid == 0) { // 子进程 // 关闭子进程中未使用的管道端 close(stdout_pipe[PIPE_READ]); close(stderr_pipe[PIPE_READ]); // 将stdout和stderr设置为无缓冲模式 setvbuf(stdout, NULL, _IONBF, 0); // 无缓冲stdout setvbuf(stderr, NULL, _IONBF, 0); // 无缓冲stderr // 将stdout和stderr重定向到管道 dup2(stdout_pipe[PIPE_WRITE], STDOUT_FILENO); dup2(stderr_pipe[PIPE_WRITE], STDERR_FILENO); // 关闭管道写入端,因为已完成重定向 close(stdout_pipe[PIPE_WRITE]); close(stderr_pipe[PIPE_WRITE]); // 子进程运行tcpdump(示例) execlp("timeout", "timeout", "5s", "tcpdump", "src", "port", "41972", (char *)NULL); // 如果execlp执行失败 perror("execlp"); exit(1); } else { // 父进程 char buffer[256]; int status; ssize_t nbytes; // 关闭父进程中未使用的管道端 close(stdout_pipe[PIPE_WRITE]); close(stderr_pipe[PIPE_WRITE]); // 将两个管道文件描述符设置为非阻塞模式 set_nonblocking(stdout_pipe[PIPE_READ]); set_nonblocking(stderr_pipe[PIPE_READ]); // 父进程以非阻塞模式从管道读取内容 printf("Parent is reading from child's stdout:\n"); while (1) { // 尝试从stdout管道读取 nbytes = read(stdout_pipe[PIPE_READ], buffer, sizeof(buffer) - 1); if (nbytes > 0) { buffer[nbytes] = '\0'; // 字符串末尾添加空字符 printf("%s", buffer); } else if (nbytes == 0) { // 到达EOF(子进程已关闭其管道端) break; } else if (errno != EAGAIN && errno != EWOULDBLOCK) { // 处理其他错误 perror("read stdout"); break; } // 尝试从stderr管道读取 nbytes = read(stderr_pipe[PIPE_READ], buffer, sizeof(buffer) - 1); if (nbytes > 0) { buffer[nbytes] = '\0'; // 字符串末尾添加空字符 printf("%s", buffer); } else if (nbytes == 0) { // 到达EOF(子进程已关闭其管道端) break; } else if (errno != EAGAIN && errno != EWOULDBLOCK) { // 处理其他错误 perror("read stderr"); break; } // 短暂休眠以避免死循环(可按需调整) usleep(100000); // 休眠100毫秒 } // 等待子进程退出 waitpid(pid, &status, 0); // 关闭父进程中的管道读取端 close(stdout_pipe[PIPE_READ]); close(stderr_pipe[PIPE_READ]); } return 0; }
错误分析与解决方案
核心错误原因
setvbuf调用时机错误:你在子进程中先调用setvbuf设置无缓冲,之后才用dup2重定向stdout/stderr。setvbuf是针对原有的stdout流生效的,重定向后新的文件描述符对应的流并没有被设置为无缓冲,等于白做。- tcpdump本身的缓冲策略:默认情况下,当stdout指向管道时,tcpdump会使用全缓冲(4096字节),不会实时输出内容,必须等缓冲区满才会写入管道。
解决步骤与修改后的代码
修改后的代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/wait.h> #include <string.h> #include <errno.h> #define PIPE_READ 0 #define PIPE_WRITE 1 void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { perror("fcntl F_GETFL"); exit(1); } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { perror("fcntl F_SETFL"); exit(1); } } int main() { int stdout_pipe[2], stderr_pipe[2]; pid_t pid; int stdout_eof = 0, stderr_eof = 0; if (pipe(stdout_pipe) == -1) { perror("pipe stdout"); exit(1); } if (pipe(stderr_pipe) == -1) { perror("pipe stderr"); exit(1); } pid = fork(); if (pid == -1) { perror("fork"); exit(1); } if (pid == 0) { // 子进程 close(stdout_pipe[PIPE_READ]); close(stderr_pipe[PIPE_READ]); // 先重定向,再设置缓冲 dup2(stdout_pipe[PIPE_WRITE], STDOUT_FILENO); dup2(stderr_pipe[PIPE_WRITE], STDERR_FILENO); close(stdout_pipe[PIPE_WRITE]); close(stderr_pipe[PIPE_WRITE]); // 重定向后设置stdout和stderr为无缓冲 setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); // 添加-l参数让tcpdump强制行缓冲输出 execlp("timeout", "timeout", "5s", "tcpdump", "-l", "src", "port", "41972", (char *)NULL); perror("execlp"); exit(1); } else { // 父进程 char buffer[256]; int status; ssize_t nbytes; close(stdout_pipe[PIPE_WRITE]); close(stderr_pipe[PIPE_WRITE]); set_nonblocking(stdout_pipe[PIPE_READ]); set_nonblocking(stderr_pipe[PIPE_READ]); printf("Parent is reading from child's stdout:\n"); while (!stdout_eof || !stderr_eof) { // 读取stdout if (!stdout_eof) { nbytes = read(stdout_pipe[PIPE_READ], buffer, sizeof(buffer) - 1); if (nbytes > 0) { buffer[nbytes] = '\0'; printf("%s", buffer); } else if (nbytes == 0) { stdout_eof = 1; } else if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("read stdout"); stdout_eof = 1; } } // 读取stderr if (!stderr_eof) { nbytes = read(stderr_pipe[PIPE_READ], buffer, sizeof(buffer) - 1); if (nbytes > 0) { buffer[nbytes] = '\0'; printf("%s", buffer); } else if (nbytes == 0) { stderr_eof = 1; } else if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("read stderr"); stderr_eof = 1; } } // 非阻塞检查子进程是否退出,避免无限等待 if (waitpid(pid, &status, WNOHANG) > 0) { // 子进程退出后,读取剩余的输出数据 while (!stdout_eof || !stderr_eof) { if (!stdout_eof) { nbytes = read(stdout_pipe[PIPE_READ], buffer, sizeof(buffer) - 1); if (nbytes > 0) { buffer[nbytes] = '\0'; printf("%s", buffer); } else if (nbytes == 0) { stdout_eof = 1; } else if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("read stdout after child exit"); stdout_eof = 1; } } if (!stderr_eof) { nbytes = read(stderr_pipe[PIPE_READ], buffer, sizeof(buffer) - 1); if (nbytes > 0) { buffer[nbytes] = '\0'; printf("%s", buffer); } else if (nbytes == 0) { stderr_eof = 1; } else if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("read stderr after child exit"); stderr_eof = 1; } } } break; } usleep(100000); } close(stdout_pipe[PIPE_READ]); close(stderr_pipe[PIPE_READ]); } return 0; }
关键修改说明
- 给tcpdump添加
-l参数:这是最核心的修改,该参数强制tcpdump使用行缓冲模式,每捕获到一条数据包就立即输出一行内容,不再等待缓冲区填满。 - 调整
setvbuf调用顺序:先完成dup2重定向,再设置流的缓冲模式,确保设置对重定向后的stdout/stderr生效。 - 优化父进程循环逻辑:
- 增加
stdout_eof和stderr_eof标记,只有当两个管道都到达EOF时才退出循环,避免提前终止读取。 - 加入
waitpid的非阻塞检查,子进程退出后继续读取剩余的输出数据,防止内容丢失。
- 增加
内容的提问来源于stack exchange,提问作者Mustafa Chelik
相关产品推荐
相关产品推荐

