带管道的多进程程序中的意外 scanf 行为
unexpected scanf behavior in multi-process program with pipes
作为 UNIX 中的一个练习 OS class 我应该开发一个程序,其中包含两个通过管道进行通信的同级进程。一个进程(生产者)应该从 stdout 读取字符串并将它们发送到另一个进程(消费者),然后该进程必须将字符串转换为大写并将其再次打印到 stdout。字符串“end”终止了兄弟进程和父进程。
该程序运行良好,但在尝试从用户那里读取整个短语而不是单个字符串时出现故障。
这是我主要的一部分:
int main(void) {
int child_status, fd[2];
if (pipe(fd)) {
fprintf(stderr, "Error: Could not create pipe.\n");
return EXIT_FAILURE;
}
if (!fork()) {
// child1
producer(fd);
exit(0);
} else if (!fork()) {
// child2
consumer(fd);
exit(0);
} else {
// father
pid_t pid;
...
生产者进程:
void producer(int *fd) {
char buff[BUFFSIZE];
char *line;
close(fd[0]);
while (1) {
fflush(stdout);
fprintf(stdout, "Insert string:\t");
scanf("%[^\n]s%*c", buff);
fflush(stdout);
line = strdup(buff);
printToPipe(fd[1], line);
if (!strcmp(line, "end")) // Special string terminates process
return;
sleep(1);
}
return;
}
以及消费者进程:
void consumer(int *fd) {
char *buff;
close(fd[1]);
while (1) {
if ( (buff = readFromPipe(fd[0])) == NULL ) {
close(fd[0]);
return;
}
fprintf(stdout, "%s\n", strToUpper(buff));
fflush(stdout);
}
return;
}
printToPipe() 和 readfromPipe() 直接实现了 read() 和 write() 系统调用。
意外行为:
一旦输入第一个短语并将其转换为大写,程序就会进入无限循环,打印出提示和第一个大写短语(即:Insert string: TEST TEST
)。特别是:
- 尽管我考虑了 \n 字符 (
scanf("%[^\n]s%*c", buff);
),但随后的 scanfs 将被忽略。如果我检查 scanf return 值,它在第一次之后的所有迭代中都是 0,
- 似乎消费者通过它的输出“自我维持”自己:它不等待通过管道的新输入并继续打印转换后的短语。
subsequent scanfs are ignored despite me accounting for the \n character
代码从不读取 '\n'
scanf("%[^\n]s%*c", buff);
是个问题.
"%[^\n]"
将无限数量的非 '\n
字符读入 buff
,可能溢出 buff
。比gets()
还差。如果至少读取了 1 个非 '\n'
字符,则将 空字符 附加到 buff
,否则扫描停止。
"s"
匹配一个 's'
,这在上述之后不是预期的 - 只有一个 '\n'
,所以扫描停止而不消耗任何 '\n'
。
格式的其余部分永远不会执行。
代码从不检查输入函数的 return 值
通过检查scanf()
的return值,buff
的内容可能不变或不确定。
// scanf("%[^\n]s%*c", buff);
if (scanf("%[^\n]s%*c", buff) == 1) {
; // OK to use `buff`
}
OP 的 scanf()
可能会占用第一行的大部分内容,将 '\n'
留在 stdin
中,但是第二个 scanf()
调用肯定会在尝试时立即停止阅读第一行的 '\n'
。这可能会使 buff
保持不变。
使用 fgets()
从 stdin
读取 行 的输入并保存为 字符串 。
// scanf("%[^\n]s%*c", buff);
if (fgets(buff, sizeof buff, stdin) == NULL) {
; // Handle end-of-file or rare input error
break;
}
要从输入行中删除 '\n'
,请参阅此 answer 和其他内容。
代码可以在字符串中保留 '\n'
并且在打印时不附加一个。
//fprintf(stdout, "%s\n", strToUpper(buff));
fprintf(stdout, "%s", strToUpper(buff));
这将更好地处理超过 BUFFSIZE
的长输入行,因为多余的字符只是留给下一次迭代。
OP 可能需要调整 strcmp(line, "end")
测试以考虑可能的尾随 '\n'
。
One process (producer) is supposed to read strings from stdout ....
详细信息:生产者不是从 stdout
读取 字符串 ,而是从 stdin
读取 行 进入 string.
作为 UNIX 中的一个练习 OS class 我应该开发一个程序,其中包含两个通过管道进行通信的同级进程。一个进程(生产者)应该从 stdout 读取字符串并将它们发送到另一个进程(消费者),然后该进程必须将字符串转换为大写并将其再次打印到 stdout。字符串“end”终止了兄弟进程和父进程。
该程序运行良好,但在尝试从用户那里读取整个短语而不是单个字符串时出现故障。
这是我主要的一部分:
int main(void) {
int child_status, fd[2];
if (pipe(fd)) {
fprintf(stderr, "Error: Could not create pipe.\n");
return EXIT_FAILURE;
}
if (!fork()) {
// child1
producer(fd);
exit(0);
} else if (!fork()) {
// child2
consumer(fd);
exit(0);
} else {
// father
pid_t pid;
...
生产者进程:
void producer(int *fd) {
char buff[BUFFSIZE];
char *line;
close(fd[0]);
while (1) {
fflush(stdout);
fprintf(stdout, "Insert string:\t");
scanf("%[^\n]s%*c", buff);
fflush(stdout);
line = strdup(buff);
printToPipe(fd[1], line);
if (!strcmp(line, "end")) // Special string terminates process
return;
sleep(1);
}
return;
}
以及消费者进程:
void consumer(int *fd) {
char *buff;
close(fd[1]);
while (1) {
if ( (buff = readFromPipe(fd[0])) == NULL ) {
close(fd[0]);
return;
}
fprintf(stdout, "%s\n", strToUpper(buff));
fflush(stdout);
}
return;
}
printToPipe() 和 readfromPipe() 直接实现了 read() 和 write() 系统调用。
意外行为:
一旦输入第一个短语并将其转换为大写,程序就会进入无限循环,打印出提示和第一个大写短语(即:Insert string: TEST TEST
)。特别是:
- 尽管我考虑了 \n 字符 (
scanf("%[^\n]s%*c", buff);
),但随后的 scanfs 将被忽略。如果我检查 scanf return 值,它在第一次之后的所有迭代中都是 0, - 似乎消费者通过它的输出“自我维持”自己:它不等待通过管道的新输入并继续打印转换后的短语。
subsequent scanfs are ignored despite me accounting for the \n character
代码从不读取 '\n'
scanf("%[^\n]s%*c", buff);
是个问题
"%[^\n]"
将无限数量的非'\n
字符读入buff
,可能溢出buff
。比gets()
还差。如果至少读取了 1 个非'\n'
字符,则将 空字符 附加到buff
,否则扫描停止。"s"
匹配一个's'
,这在上述之后不是预期的 - 只有一个'\n'
,所以扫描停止而不消耗任何'\n'
。格式的其余部分永远不会执行。
代码从不检查输入函数的 return 值
通过检查scanf()
的return值,buff
的内容可能不变或不确定。
// scanf("%[^\n]s%*c", buff);
if (scanf("%[^\n]s%*c", buff) == 1) {
; // OK to use `buff`
}
OP 的 scanf()
可能会占用第一行的大部分内容,将 '\n'
留在 stdin
中,但是第二个 scanf()
调用肯定会在尝试时立即停止阅读第一行的 '\n'
。这可能会使 buff
保持不变。
使用 fgets()
从 stdin
读取 行 的输入并保存为 字符串 。
// scanf("%[^\n]s%*c", buff);
if (fgets(buff, sizeof buff, stdin) == NULL) {
; // Handle end-of-file or rare input error
break;
}
要从输入行中删除 '\n'
,请参阅此 answer 和其他内容。
代码可以在字符串中保留 '\n'
并且在打印时不附加一个。
//fprintf(stdout, "%s\n", strToUpper(buff));
fprintf(stdout, "%s", strToUpper(buff));
这将更好地处理超过 BUFFSIZE
的长输入行,因为多余的字符只是留给下一次迭代。
OP 可能需要调整 strcmp(line, "end")
测试以考虑可能的尾随 '\n'
。
One process (producer) is supposed to read strings from stdout ....
详细信息:生产者不是从 stdout
读取 字符串 ,而是从 stdin
读取 行 进入 string.