C - 如何通过管道传输到只读文件的程序
C - How to pipe to a program that read only from file
我想将一个字符串通过管道传输到一个程序,该程序只从文件中读取输入,而不是从标准输入中读取输入。从 bash 使用它,我可以做类似
的事情
echo "hi" | program /dev/stdin
我想从 C 代码中复制这种行为。我做的是这个
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
int main() {
pid_t pid;
int rv;
int to_ext_program_pipe[2];
int to_my_program_pipe[2];
if(pipe(to_ext_program_pipe)) {
fprintf(stderr,"Pipe error!\n");
exit(1);
}
if(pipe(to_my_program_pipe)) {
fprintf(stderr,"Pipe error!\n");
exit(1);
}
if( (pid=fork()) == -1) {
fprintf(stderr,"Fork error. Exiting.\n");
exit(1);
}
if(pid) {
close(to_my_program_pipe[1]);
close(to_ext_program_pipe[0]);
char string_to_write[] = "this is the string to write";
write(to_ext_program_pipe[1], string_to_write, strlen(string_to_write) + 1);
close(to_ext_program_pipe[1]);
wait(&rv);
if(rv != 0) {
fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
exit(1);
}
char *string_to_read;
char ch[1];
size_t len = 0;
string_to_read = malloc(sizeof(char));
if(!string_to_read) {
fprintf(stderr, "%s\n", "Error while allocating memory");
exit(1);
}
while(read(to_my_program_pipe[0], ch, 1) == 1) {
string_to_read[len]=ch[0];
len++;
string_to_read = realloc(string_to_read, len*sizeof(char));
if(!string_to_read) {
fprintf(stderr, "%s\n", "Error while allocating memory");
}
string_to_read[len] = '[=11=]';
}
close(to_my_program_pipe[0]);
printf("Output: %s\n", string_to_read);
free(string_to_read);
} else {
close(to_ext_program_pipe[1]);
close(to_my_program_pipe[0]);
dup2(to_ext_program_pipe[0],0);
dup2(to_my_program_pipe[1],1);
if(execlp("ext_program", "ext_program", "/dev/stdin" , NULL) == -1) {
fprintf(stderr,"execlp Error!");
exit(1);
}
close(to_ext_program_pipe[0]);
close(to_my_program_pipe[1]);
}
return 0;
}
它不工作。
编辑
我没有得到 ext_program
输出,应该保存在 string_to_read
中。该程序只是挂起。我可以看到 ext_program
被执行了,但是我没有得到任何东西
我想知道是否有错误,或者我想要的东西无法完成。我也知道另一种方法是使用命名管道。
编辑 2:更多细节
由于我的程序仍然无法运行,我post完整的代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
int main() {
pid_t pid;
int rv;
int to_phantomjs_pipe[2];
int to_my_program_pipe[2];
if(pipe(to_phantomjs_pipe)) {
fprintf(stderr,"Pipe error!\n");
exit(1);
}
if(pipe(to_my_program_pipe)) {
fprintf(stderr,"Pipe error!\n");
exit(1);
}
if( (pid=fork()) == -1) {
fprintf(stderr,"Fork error. Exiting.\n");
exit(1);
}
if(pid) {
close(to_my_program_pipe[1]);
close(to_phantomjs_pipe[0]);
char jsToExectue[] = "var page=require(\'webpage\').create();page.onInitialized=function(){page.evaluate(function(){delete window._phantom;delete window.callPhantom;});};page.onResourceRequested=function(requestData,request){if((/http:\/\/.+\?\\.css/gi).test(requestData[\'url\'])||requestData.headers[\'Content-Type\']==\'text/css\'){request.abort();}};page.settings.loadImage=false;page.settings.userAgent=\'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36\';page.open(\'https://whosebug.com\',function(status){if(status!==\'success\'){phantom.exit(1);}else{console.log(page.content);phantom.exit();}});";
write(to_phantomjs_pipe[1], jsToExectue, strlen(jsToExectue) + 1);
close(to_phantomjs_pipe[1]);
int read_chars;
int BUFF=1024;
char *str;
char ch[BUFF];
size_t len = 0;
str = malloc(sizeof(char));
if(!str) {
fprintf(stderr, "%s\n", "Error while allocating memory");
exit(1);
}
str[0] = '[=12=]';
while( (read_chars = read(to_my_program_pipe[0], ch, BUFF)) > 0)
{
len += read_chars;
str = realloc(str, (len + 1)*sizeof(char));
if(!str) {
fprintf(stderr, "%s\n", "Error while allocating memory");
}
strcat(str, ch);
str[len] = '[=12=]';
memset(ch, '[=12=]', BUFF*sizeof(ch[0]));
}
close(to_my_program_pipe[0]);
printf("%s\n", str);
free(str);
wait(&rv);
if(rv != 0) {
fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
exit(1);
}
} else {
dup2(to_phantomjs_pipe[0],0);
dup2(to_my_program_pipe[1],1);
close(to_phantomjs_pipe[1]);
close(to_my_program_pipe[0]);
close(to_phantomjs_pipe[0]);
close(to_my_program_pipe[1]);
execlp("phantomjs", "phantomjs", "--ssl-protocol=TLSv1", "/dev/stdin" , (char *)NULL);
}
return 0;
}
我想做的是将脚本传递给 phantomjs 以通过管道执行,然后将结果 HTML 作为字符串读取。我按照说明修改了代码,但是 phantomjs 仍然没有从 stdin 读取。
我通过创建一个将脚本字符串写入文件然后正常执行 phantomjs 的愚蠢程序来测试脚本字符串并且有效。
我还尝试执行
execlp("phantomjs", "phantomjs", "--ssl-protocol=TLSv1", "path_to_script_file" , (char *)NULL);
并且它也有效,显示了输出 HTML 。
使用管道时无效。
终于有了解释
对 PhantomJS 的一些实验表明问题是在发送到 PhantomJS 的 JavaScript 程序末尾写了一个空字节。
这突出了两个错误:
- 问题中的程序发送了一个不必要的空字节。
- PhantomJS 2.1.1(在 Mac 运行ning macOS High Sierra 10.13.3 上)在其他有效程序后跟空字节时挂起[=131=]
问题中的代码包含:
write(to_phantomjs_pipe[1], jsToExectue, strlen(jsToExectue) + 1);
+ 1
表示终止字符串的空字节也写入phantomjs
。写入该空字节会导致 phantomjs
挂起。这无异于一个错误——当然不清楚为什么 PhantomJS 在没有检测到 EOF(没有更多数据要来)的情况下挂起,也没有给出错误等。
将该行更改为:
write(to_phantomjs_pipe[1], jsToExectue, strlen(jsToExectue));
并且代码按预期工作——至少在 Mac 运行ning macOS High Sierra 10.13.3.
上使用 PhantomJS 2.1.1
初步分析
您没有关闭子项中足够的文件描述符。
显示的子代码是:
} else {
close(to_ext_program_pipe[1]);
close(to_my_program_pipe[0]);
dup2(to_ext_program_pipe[0],0);
dup2(to_my_program_pipe[1],1);
if(execlp("ext_program", "ext_program", "/dev/stdin" , NULL) == -1) {
fprintf(stderr,"execlp Error!");
exit(1);
}
close(to_ext_program_pipe[0]);
close(to_my_program_pipe[1]);
}
最后两个 close()
语句永远不会执行;他们需要出现在 execlp()
.
之前
您需要的是:
} else {
dup2(to_ext_program_pipe[0], 0);
dup2(to_my_program_pipe[1], 1);
close(to_ext_program_pipe[0]);
close(to_ext_program_pipe[1]);
close(to_my_program_pipe[0]);
close(to_my_program_pipe[1]);
execlp("ext_program", "ext_program", "/dev/stdin" , NULL);
fprintf(stderr, "execlp Error!\n");
exit(1);
}
您可以重新排序它拆分 close()
个调用,但最好将它们重新组合,如图所示。
注意不需要测试execlp()
是否失败。如果是 returns,则失败。如果成功,则不会 return.
可能还有其他问题。父进程等待子进程退出,然后再从子进程读取任何内容。然而,如果子进程试图写入比管道容纳的更多的数据,进程将挂起,等待某个进程(必须是父进程)读取管道。由于他们都在等待对方做某事,然后他们才会做对方正在等待的事情,这是(或者,至少,可能是)一个僵局。
你也应该修改父进程做等待前的读取。
if (pid) {
close(to_my_program_pipe[1]);
close(to_ext_program_pipe[0]);
char string_to_write[] = "this is the string to write";
write(to_ext_program_pipe[1], string_to_write, strlen(string_to_write) + 1);
close(to_ext_program_pipe[1]);
char *string_to_read;
char ch[1];
size_t len = 0;
string_to_read = malloc(sizeof(char));
if(!string_to_read) {
fprintf(stderr, "%s\n", "Error while allocating memory");
exit(1);
}
while (read(to_my_program_pipe[0], ch, 1) == 1) {
string_to_read[len] = ch[0];
len++;
string_to_read = realloc(string_to_read, len*sizeof(char));
if (!string_to_read) {
fprintf(stderr, "%s\n", "Error while allocating memory\n");
exit(1);
}
string_to_read[len] = '[=14=]';
}
close(to_my_program_pipe[0]);
printf("Output: %s\n", string_to_read);
free(string_to_read);
wait(&rv);
if (rv != 0) {
fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
exit(1);
}
} …
我还会重写代码以大块读取(1024 字节或更多)。只是不要复制比读取的 return 多的数据,仅此而已。重复使用 realloc()
为缓冲区再分配一个字节最终会非常慢。如果只有几个字节的数据,那无关紧要;如果有千字节或更多数据要处理,这将很重要。
后来: 由于 PhantomJS 程序生成了超过 90 KiB 的数据以响应它发送的消息,这是问题的一个因素——或者如果它是不适用于 PhantomJS 中的 hang-on-null-byte 错误。
仍有问题 2018-02-03
我将修改后的代码提取到一个程序中(pipe89.c
,编译为 pipe89
)。当 space 分配更改时,我遇到了不一致的崩溃。我最终意识到你重新分配的一个字节太少 space — 它花费的时间比应该完成的时间长得多(但如果 Valgrind 可用于 macOS High Sierra 会有所帮助 — 目前还没有)。
这是带有调试信息注释输出的固定代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
int rv;
int to_ext_program_pipe[2];
int to_my_program_pipe[2];
if (pipe(to_ext_program_pipe))
{
fprintf(stderr, "Pipe error!\n");
exit(1);
}
if (pipe(to_my_program_pipe))
{
fprintf(stderr, "Pipe error!\n");
exit(1);
}
if ((pid = fork()) == -1)
{
fprintf(stderr, "Fork error. Exiting.\n");
exit(1);
}
if (pid)
{
close(to_my_program_pipe[1]);
close(to_ext_program_pipe[0]);
char string_to_write[] = "this is the string to write";
write(to_ext_program_pipe[1], string_to_write, sizeof(string_to_write) - 1);
close(to_ext_program_pipe[1]);
char ch[1];
size_t len = 0;
char *string_to_read = malloc(sizeof(char));
if (string_to_read == 0)
{
fprintf(stderr, "%s\n", "Error while allocating memory");
exit(1);
}
string_to_read[len] = '[=15=]';
while (read(to_my_program_pipe[0], ch, 1) == 1)
{
//fprintf(stderr, "%3zu: got %3d [%c]\n", len, ch[0], ch[0]); fflush(stderr);
string_to_read[len++] = ch[0];
char *new_space = realloc(string_to_read, len + 1); // KEY CHANGE is " + 1"
//if (new_space != string_to_read)
// fprintf(stderr, "Move: len %zu old %p vs new %p\n", len, (void *)string_to_read, (void *)new_space);
if (new_space == 0)
{
fprintf(stderr, "Error while allocating %zu bytes memory\n", len);
exit(1);
}
string_to_read = new_space;
string_to_read[len] = '[=15=]';
}
close(to_my_program_pipe[0]);
printf("Output: %zu (%zu) [%s]\n", len, strlen(string_to_read), string_to_read);
free(string_to_read);
wait(&rv);
if (rv != 0)
{
fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
exit(1);
}
}
else
{
dup2(to_ext_program_pipe[0], 0);
dup2(to_my_program_pipe[1], 1);
close(to_ext_program_pipe[0]);
close(to_ext_program_pipe[1]);
close(to_my_program_pipe[0]);
close(to_my_program_pipe[1]);
execlp("ext_program", "ext_program", "/dev/stdin", NULL);
fprintf(stderr, "execlp Error!\n");
exit(1);
}
return 0;
}
它是在一个程序上测试的,该程序为 27 个字节的输入写出 5590 个字节。这不像您的程序中那样大,但它证明了一点。
我仍然认为你最好不要一次重新分配一个额外的字节——扫描循环应该使用一个缓冲区,比如 1 KiB,一次最多读取 1 KiB,然后分配额外的字节space 一下子。这对内存分配系统来说强度要小得多。
问题在 2018-02-05 继续
从 Edit 2 中获取代码并仅将函数定义从 int main() {
更改为 int main(void) {
(因为我使用的编译选项不允许旧式非原型函数声明或定义,没有 void
,那不是原型),代码是
对我来说工作正常。我创建了一个代理 phantomjs
程序(来自另一个我已经在使用的程序),如下所示:
#include <stdio.h>
int main(int argc, char **argv, char **envp)
{
for (int i = 0; i < argc; i++)
printf("argv[%d] = <<%s>>\n", i, argv[i]);
for (int i = 0; envp[i] != 0; i++)
printf("envp[%d] = <<%s>>\n", i, envp[i]);
FILE *fp = fopen(argv[argc - 1], "r");
if (fp != 0)
{
int c;
while ((c = getc(fp)) != EOF)
putchar(c);
fclose(fp);
}
else
fprintf(stderr, "%s: failed to open file %s for reading\n",
argv[0], argv[argc-1]);
return(0);
}
此代码回显参数列表、环境,然后打开名为最后一个参数的文件并将其复制到标准输出。 (由于对 argv[argc-1]
的特殊处理,它是高度专业化的,但之前的代码偶尔对调试复杂的 shell 脚本很有用。)
当我 运行 你的程序使用这个 'phantomjs
' 时,我得到了我期望的输出:
argv[0] = <<phantomjs>>
argv[1] = <<--ssl-protocol=TLSv1>>
argv[2] = <</dev/stdin>>
envp[0] = <<MANPATH=/Users/jleffler/man:/Users/jleffler/share/man:/Users/jleffler/oss/share/man:/Users/jleffler/oss/rcs/man:/usr/local/mysql/man:/opt/gcc/v7.3.0/share/man:/Users/jleffler/perl/v5.24.0/man:/usr/local/man:/usr/local/share/man:/usr/share/man:/opt/gnu/share/man>>
envp[1] = <<IXH=/opt/informix/12.10.FC6/etc/sqlhosts>>
…
envp[49] = <<HISTFILE=/Users/jleffler/.bash.jleffler>>
envp[50] = <<_=./pipe31>>
var page=require('webpage').create();page.onInitialized=function(){page.evaluate(function(){delete window._phantom;delete window.callPhantom;});};page.onResourceRequested=function(requestData,request){if((/http:\/\/.+?\.css/gi).test(requestData['url'])||requestData.headers['Content-Type']=='text/css'){request.abort();}};page.settings.loadImage=false;page.settings.userAgent='Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36';page.open('https://whosebug.com',function(status){if(status!=='success'){phantom.exit(1);}else{console.log(page.content);phantom.exit();}});
在这一点上,我不得不把矛头指向phantomjs
在你的环境中;当您执行相当于:
时,它的行为似乎并不像预期的那样
echo "$JS_PROG" | phantomjs /dev/stdin | cat
当然,我无法再重现您的问题。
你应该使用我的代理 phantomjs
代码并使用它来代替真正的 phantomjs
看看你得到了什么。
- 如果您得到类似于我所展示的输出,那么问题出在真正的
phantomjs
。
- 如果您没有得到与我展示的类似的输出,那么您的代码从更新到问题可能存在问题。
之后: 请注意,因为 printf()
使用 %s
来打印数据,所以它不会注意到发送给子进程的无关空字节.
在 pipe(7) man 中写到你应该尽快从管道读取:
If a process attempts to write to a
full pipe (see below), then write(2) blocks until sufficient data has
been read from the pipe to allow the write to complete. Nonblocking
I/O is possible by using the fcntl(2) F_SETFL operation to enable the
O_NONBLOCK open file status flag.
和
A pipe has a limited capacity. If the pipe is full, then a write(2)
will block or fail, depending on whether the O_NONBLOCK flag is set
(see below). Different implementations have different limits for the
pipe capacity. Applications should not rely on a particular
capacity: an application should be designed so that a reading process
consumes data as soon as it is available, so that a writing process
does not remain blocked.
在您编写的代码中,等待然后才读取
write(to_ext_program_pipe[1], string_to_write, strlen(string_to_write) + 1);
close(to_ext_program_pipe[1]);
wait(&rv);
//...
while(read(to_my_program_pipe[0], ch, 1) == 1) {
//...
也许管道已满或ext_program
正在等待读取数据,您应该wait()
仅在读取之后。
我想将一个字符串通过管道传输到一个程序,该程序只从文件中读取输入,而不是从标准输入中读取输入。从 bash 使用它,我可以做类似
的事情echo "hi" | program /dev/stdin
我想从 C 代码中复制这种行为。我做的是这个
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
int main() {
pid_t pid;
int rv;
int to_ext_program_pipe[2];
int to_my_program_pipe[2];
if(pipe(to_ext_program_pipe)) {
fprintf(stderr,"Pipe error!\n");
exit(1);
}
if(pipe(to_my_program_pipe)) {
fprintf(stderr,"Pipe error!\n");
exit(1);
}
if( (pid=fork()) == -1) {
fprintf(stderr,"Fork error. Exiting.\n");
exit(1);
}
if(pid) {
close(to_my_program_pipe[1]);
close(to_ext_program_pipe[0]);
char string_to_write[] = "this is the string to write";
write(to_ext_program_pipe[1], string_to_write, strlen(string_to_write) + 1);
close(to_ext_program_pipe[1]);
wait(&rv);
if(rv != 0) {
fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
exit(1);
}
char *string_to_read;
char ch[1];
size_t len = 0;
string_to_read = malloc(sizeof(char));
if(!string_to_read) {
fprintf(stderr, "%s\n", "Error while allocating memory");
exit(1);
}
while(read(to_my_program_pipe[0], ch, 1) == 1) {
string_to_read[len]=ch[0];
len++;
string_to_read = realloc(string_to_read, len*sizeof(char));
if(!string_to_read) {
fprintf(stderr, "%s\n", "Error while allocating memory");
}
string_to_read[len] = '[=11=]';
}
close(to_my_program_pipe[0]);
printf("Output: %s\n", string_to_read);
free(string_to_read);
} else {
close(to_ext_program_pipe[1]);
close(to_my_program_pipe[0]);
dup2(to_ext_program_pipe[0],0);
dup2(to_my_program_pipe[1],1);
if(execlp("ext_program", "ext_program", "/dev/stdin" , NULL) == -1) {
fprintf(stderr,"execlp Error!");
exit(1);
}
close(to_ext_program_pipe[0]);
close(to_my_program_pipe[1]);
}
return 0;
}
它不工作。
编辑
我没有得到 ext_program
输出,应该保存在 string_to_read
中。该程序只是挂起。我可以看到 ext_program
被执行了,但是我没有得到任何东西
我想知道是否有错误,或者我想要的东西无法完成。我也知道另一种方法是使用命名管道。
编辑 2:更多细节
由于我的程序仍然无法运行,我post完整的代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
int main() {
pid_t pid;
int rv;
int to_phantomjs_pipe[2];
int to_my_program_pipe[2];
if(pipe(to_phantomjs_pipe)) {
fprintf(stderr,"Pipe error!\n");
exit(1);
}
if(pipe(to_my_program_pipe)) {
fprintf(stderr,"Pipe error!\n");
exit(1);
}
if( (pid=fork()) == -1) {
fprintf(stderr,"Fork error. Exiting.\n");
exit(1);
}
if(pid) {
close(to_my_program_pipe[1]);
close(to_phantomjs_pipe[0]);
char jsToExectue[] = "var page=require(\'webpage\').create();page.onInitialized=function(){page.evaluate(function(){delete window._phantom;delete window.callPhantom;});};page.onResourceRequested=function(requestData,request){if((/http:\/\/.+\?\\.css/gi).test(requestData[\'url\'])||requestData.headers[\'Content-Type\']==\'text/css\'){request.abort();}};page.settings.loadImage=false;page.settings.userAgent=\'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36\';page.open(\'https://whosebug.com\',function(status){if(status!==\'success\'){phantom.exit(1);}else{console.log(page.content);phantom.exit();}});";
write(to_phantomjs_pipe[1], jsToExectue, strlen(jsToExectue) + 1);
close(to_phantomjs_pipe[1]);
int read_chars;
int BUFF=1024;
char *str;
char ch[BUFF];
size_t len = 0;
str = malloc(sizeof(char));
if(!str) {
fprintf(stderr, "%s\n", "Error while allocating memory");
exit(1);
}
str[0] = '[=12=]';
while( (read_chars = read(to_my_program_pipe[0], ch, BUFF)) > 0)
{
len += read_chars;
str = realloc(str, (len + 1)*sizeof(char));
if(!str) {
fprintf(stderr, "%s\n", "Error while allocating memory");
}
strcat(str, ch);
str[len] = '[=12=]';
memset(ch, '[=12=]', BUFF*sizeof(ch[0]));
}
close(to_my_program_pipe[0]);
printf("%s\n", str);
free(str);
wait(&rv);
if(rv != 0) {
fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
exit(1);
}
} else {
dup2(to_phantomjs_pipe[0],0);
dup2(to_my_program_pipe[1],1);
close(to_phantomjs_pipe[1]);
close(to_my_program_pipe[0]);
close(to_phantomjs_pipe[0]);
close(to_my_program_pipe[1]);
execlp("phantomjs", "phantomjs", "--ssl-protocol=TLSv1", "/dev/stdin" , (char *)NULL);
}
return 0;
}
我想做的是将脚本传递给 phantomjs 以通过管道执行,然后将结果 HTML 作为字符串读取。我按照说明修改了代码,但是 phantomjs 仍然没有从 stdin 读取。
我通过创建一个将脚本字符串写入文件然后正常执行 phantomjs 的愚蠢程序来测试脚本字符串并且有效。
我还尝试执行 execlp("phantomjs", "phantomjs", "--ssl-protocol=TLSv1", "path_to_script_file" , (char *)NULL);
并且它也有效,显示了输出 HTML 。
使用管道时无效。
终于有了解释
对 PhantomJS 的一些实验表明问题是在发送到 PhantomJS 的 JavaScript 程序末尾写了一个空字节。 这突出了两个错误:
- 问题中的程序发送了一个不必要的空字节。
- PhantomJS 2.1.1(在 Mac 运行ning macOS High Sierra 10.13.3 上)在其他有效程序后跟空字节时挂起[=131=]
问题中的代码包含:
write(to_phantomjs_pipe[1], jsToExectue, strlen(jsToExectue) + 1);
+ 1
表示终止字符串的空字节也写入phantomjs
。写入该空字节会导致 phantomjs
挂起。这无异于一个错误——当然不清楚为什么 PhantomJS 在没有检测到 EOF(没有更多数据要来)的情况下挂起,也没有给出错误等。
将该行更改为:
write(to_phantomjs_pipe[1], jsToExectue, strlen(jsToExectue));
并且代码按预期工作——至少在 Mac 运行ning macOS High Sierra 10.13.3.
上使用 PhantomJS 2.1.1初步分析
您没有关闭子项中足够的文件描述符。
显示的子代码是:
} else {
close(to_ext_program_pipe[1]);
close(to_my_program_pipe[0]);
dup2(to_ext_program_pipe[0],0);
dup2(to_my_program_pipe[1],1);
if(execlp("ext_program", "ext_program", "/dev/stdin" , NULL) == -1) {
fprintf(stderr,"execlp Error!");
exit(1);
}
close(to_ext_program_pipe[0]);
close(to_my_program_pipe[1]);
}
最后两个 close()
语句永远不会执行;他们需要出现在 execlp()
.
您需要的是:
} else {
dup2(to_ext_program_pipe[0], 0);
dup2(to_my_program_pipe[1], 1);
close(to_ext_program_pipe[0]);
close(to_ext_program_pipe[1]);
close(to_my_program_pipe[0]);
close(to_my_program_pipe[1]);
execlp("ext_program", "ext_program", "/dev/stdin" , NULL);
fprintf(stderr, "execlp Error!\n");
exit(1);
}
您可以重新排序它拆分 close()
个调用,但最好将它们重新组合,如图所示。
注意不需要测试execlp()
是否失败。如果是 returns,则失败。如果成功,则不会 return.
可能还有其他问题。父进程等待子进程退出,然后再从子进程读取任何内容。然而,如果子进程试图写入比管道容纳的更多的数据,进程将挂起,等待某个进程(必须是父进程)读取管道。由于他们都在等待对方做某事,然后他们才会做对方正在等待的事情,这是(或者,至少,可能是)一个僵局。
你也应该修改父进程做等待前的读取。
if (pid) {
close(to_my_program_pipe[1]);
close(to_ext_program_pipe[0]);
char string_to_write[] = "this is the string to write";
write(to_ext_program_pipe[1], string_to_write, strlen(string_to_write) + 1);
close(to_ext_program_pipe[1]);
char *string_to_read;
char ch[1];
size_t len = 0;
string_to_read = malloc(sizeof(char));
if(!string_to_read) {
fprintf(stderr, "%s\n", "Error while allocating memory");
exit(1);
}
while (read(to_my_program_pipe[0], ch, 1) == 1) {
string_to_read[len] = ch[0];
len++;
string_to_read = realloc(string_to_read, len*sizeof(char));
if (!string_to_read) {
fprintf(stderr, "%s\n", "Error while allocating memory\n");
exit(1);
}
string_to_read[len] = '[=14=]';
}
close(to_my_program_pipe[0]);
printf("Output: %s\n", string_to_read);
free(string_to_read);
wait(&rv);
if (rv != 0) {
fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
exit(1);
}
} …
我还会重写代码以大块读取(1024 字节或更多)。只是不要复制比读取的 return 多的数据,仅此而已。重复使用 realloc()
为缓冲区再分配一个字节最终会非常慢。如果只有几个字节的数据,那无关紧要;如果有千字节或更多数据要处理,这将很重要。
后来: 由于 PhantomJS 程序生成了超过 90 KiB 的数据以响应它发送的消息,这是问题的一个因素——或者如果它是不适用于 PhantomJS 中的 hang-on-null-byte 错误。
仍有问题 2018-02-03
我将修改后的代码提取到一个程序中(pipe89.c
,编译为 pipe89
)。当 space 分配更改时,我遇到了不一致的崩溃。我最终意识到你重新分配的一个字节太少 space — 它花费的时间比应该完成的时间长得多(但如果 Valgrind 可用于 macOS High Sierra 会有所帮助 — 目前还没有)。
这是带有调试信息注释输出的固定代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
int rv;
int to_ext_program_pipe[2];
int to_my_program_pipe[2];
if (pipe(to_ext_program_pipe))
{
fprintf(stderr, "Pipe error!\n");
exit(1);
}
if (pipe(to_my_program_pipe))
{
fprintf(stderr, "Pipe error!\n");
exit(1);
}
if ((pid = fork()) == -1)
{
fprintf(stderr, "Fork error. Exiting.\n");
exit(1);
}
if (pid)
{
close(to_my_program_pipe[1]);
close(to_ext_program_pipe[0]);
char string_to_write[] = "this is the string to write";
write(to_ext_program_pipe[1], string_to_write, sizeof(string_to_write) - 1);
close(to_ext_program_pipe[1]);
char ch[1];
size_t len = 0;
char *string_to_read = malloc(sizeof(char));
if (string_to_read == 0)
{
fprintf(stderr, "%s\n", "Error while allocating memory");
exit(1);
}
string_to_read[len] = '[=15=]';
while (read(to_my_program_pipe[0], ch, 1) == 1)
{
//fprintf(stderr, "%3zu: got %3d [%c]\n", len, ch[0], ch[0]); fflush(stderr);
string_to_read[len++] = ch[0];
char *new_space = realloc(string_to_read, len + 1); // KEY CHANGE is " + 1"
//if (new_space != string_to_read)
// fprintf(stderr, "Move: len %zu old %p vs new %p\n", len, (void *)string_to_read, (void *)new_space);
if (new_space == 0)
{
fprintf(stderr, "Error while allocating %zu bytes memory\n", len);
exit(1);
}
string_to_read = new_space;
string_to_read[len] = '[=15=]';
}
close(to_my_program_pipe[0]);
printf("Output: %zu (%zu) [%s]\n", len, strlen(string_to_read), string_to_read);
free(string_to_read);
wait(&rv);
if (rv != 0)
{
fprintf(stderr, "%s %d\n", "phantomjs exit status ", rv);
exit(1);
}
}
else
{
dup2(to_ext_program_pipe[0], 0);
dup2(to_my_program_pipe[1], 1);
close(to_ext_program_pipe[0]);
close(to_ext_program_pipe[1]);
close(to_my_program_pipe[0]);
close(to_my_program_pipe[1]);
execlp("ext_program", "ext_program", "/dev/stdin", NULL);
fprintf(stderr, "execlp Error!\n");
exit(1);
}
return 0;
}
它是在一个程序上测试的,该程序为 27 个字节的输入写出 5590 个字节。这不像您的程序中那样大,但它证明了一点。
我仍然认为你最好不要一次重新分配一个额外的字节——扫描循环应该使用一个缓冲区,比如 1 KiB,一次最多读取 1 KiB,然后分配额外的字节space 一下子。这对内存分配系统来说强度要小得多。
问题在 2018-02-05 继续
从 Edit 2 中获取代码并仅将函数定义从 int main() {
更改为 int main(void) {
(因为我使用的编译选项不允许旧式非原型函数声明或定义,没有 void
,那不是原型),代码是
对我来说工作正常。我创建了一个代理 phantomjs
程序(来自另一个我已经在使用的程序),如下所示:
#include <stdio.h>
int main(int argc, char **argv, char **envp)
{
for (int i = 0; i < argc; i++)
printf("argv[%d] = <<%s>>\n", i, argv[i]);
for (int i = 0; envp[i] != 0; i++)
printf("envp[%d] = <<%s>>\n", i, envp[i]);
FILE *fp = fopen(argv[argc - 1], "r");
if (fp != 0)
{
int c;
while ((c = getc(fp)) != EOF)
putchar(c);
fclose(fp);
}
else
fprintf(stderr, "%s: failed to open file %s for reading\n",
argv[0], argv[argc-1]);
return(0);
}
此代码回显参数列表、环境,然后打开名为最后一个参数的文件并将其复制到标准输出。 (由于对 argv[argc-1]
的特殊处理,它是高度专业化的,但之前的代码偶尔对调试复杂的 shell 脚本很有用。)
当我 运行 你的程序使用这个 'phantomjs
' 时,我得到了我期望的输出:
argv[0] = <<phantomjs>>
argv[1] = <<--ssl-protocol=TLSv1>>
argv[2] = <</dev/stdin>>
envp[0] = <<MANPATH=/Users/jleffler/man:/Users/jleffler/share/man:/Users/jleffler/oss/share/man:/Users/jleffler/oss/rcs/man:/usr/local/mysql/man:/opt/gcc/v7.3.0/share/man:/Users/jleffler/perl/v5.24.0/man:/usr/local/man:/usr/local/share/man:/usr/share/man:/opt/gnu/share/man>>
envp[1] = <<IXH=/opt/informix/12.10.FC6/etc/sqlhosts>>
…
envp[49] = <<HISTFILE=/Users/jleffler/.bash.jleffler>>
envp[50] = <<_=./pipe31>>
var page=require('webpage').create();page.onInitialized=function(){page.evaluate(function(){delete window._phantom;delete window.callPhantom;});};page.onResourceRequested=function(requestData,request){if((/http:\/\/.+?\.css/gi).test(requestData['url'])||requestData.headers['Content-Type']=='text/css'){request.abort();}};page.settings.loadImage=false;page.settings.userAgent='Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36';page.open('https://whosebug.com',function(status){if(status!=='success'){phantom.exit(1);}else{console.log(page.content);phantom.exit();}});
在这一点上,我不得不把矛头指向phantomjs
在你的环境中;当您执行相当于:
echo "$JS_PROG" | phantomjs /dev/stdin | cat
当然,我无法再重现您的问题。
你应该使用我的代理
phantomjs
代码并使用它来代替真正的phantomjs
看看你得到了什么。- 如果您得到类似于我所展示的输出,那么问题出在真正的
phantomjs
。 - 如果您没有得到与我展示的类似的输出,那么您的代码从更新到问题可能存在问题。
- 如果您得到类似于我所展示的输出,那么问题出在真正的
之后: 请注意,因为 printf()
使用 %s
来打印数据,所以它不会注意到发送给子进程的无关空字节.
在 pipe(7) man 中写到你应该尽快从管道读取:
If a process attempts to write to a full pipe (see below), then write(2) blocks until sufficient data has been read from the pipe to allow the write to complete. Nonblocking I/O is possible by using the fcntl(2) F_SETFL operation to enable the O_NONBLOCK open file status flag.
和
A pipe has a limited capacity. If the pipe is full, then a write(2) will block or fail, depending on whether the O_NONBLOCK flag is set (see below). Different implementations have different limits for the pipe capacity. Applications should not rely on a particular capacity: an application should be designed so that a reading process consumes data as soon as it is available, so that a writing process does not remain blocked.
在您编写的代码中,等待然后才读取
write(to_ext_program_pipe[1], string_to_write, strlen(string_to_write) + 1);
close(to_ext_program_pipe[1]);
wait(&rv);
//...
while(read(to_my_program_pipe[0], ch, 1) == 1) {
//...
也许管道已满或ext_program
正在等待读取数据,您应该wait()
仅在读取之后。