无法使用 C 中的命名管道与 shell 脚本通信

Posted

技术标签:

【中文标题】无法使用 C 中的命名管道与 shell 脚本通信【英文标题】:Can't use named pipe from C to communicate with shell script 【发布时间】:2013-11-22 22:42:16 【问题描述】:

我有一个这样的 C 程序(复制自 here):

#include <fcntl.h>

#define PATH "testpipe"
#define MESSAGE "We are not alone"

int main()

   int fd;

   mkfifo ( PATH, 0666 );

   fd = open ( PATH, O_WRONLY );
   write ( fd, MESSAGE, sizeof ( MESSAGE ) );
   close ( fd );

   unlink ( PATH );
   return 0;

还有一个像这样的shell脚本:

echo < testpipe

一旦我执行了 C 程序,echo 语句就会返回,但是We are not alone 不会被打印出来。我也尝试过从命令行创建管道,并改用mknod,这没有什么区别。为什么这不起作用?

编辑:

很多人指出问题出在echo 而不是C,问题是,我需要它与echo(实际上是omxplayer)这样的东西一起工作,因为我正在发布视频控制命令,即p 用于暂停或q 用于退出)。因此,我将不胜感激一些答案,说明如何更改 C 代码以使其与 echo 语句一起使用,反之亦然

编辑:

我没有包含完整的代码,因为它使用 omxplayer 并且相当大,但一些用户要求它,因此,这里是我可以保留此 MWE 的最小代码:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define PIPEPATH "testpipe"
#define VIDEOPATH "Matrix.mkv"
#define MESSAGE "q"
#define VIDEOPLAYER "omxplayer"

int main()

   int fd;
   pid_t pid;
   pid_t wpid;
   int status;
   char shellCmd [ 1000 ];
   struct timespec time1, time2; //used for sleeping

   //Make pipe BEFORE forking
   mkfifo ( PIPEPATH, 0666 );

   if ( ( pid = fork () ) < 0 )
   
      perror ( "Fork Failed\n" );
      return -1;
   
   else if ( pid == 0 )
    //first child keeps pipe open
      sprintf ( shellCmd, "tail -f /dev/null > %s", PIPEPATH );
      if ( system ( shellCmd ) == -1 )
      
         printf ( "Error: %s\n", shellCmd );
         fflush(stdout);
      
   
   else
      time1.tv_sec = 1L; //sleep for 1 second to let first child issue its command
      time1.tv_nsec = 0L; //Dont worry about milli seconds

      nanosleep ( &time1, &time2 );

      if ( ( pid = fork () ) < 0 )
      
         perror ( "Fork Failed\n" );
         return -1;
      
      else if ( pid == 0 )
       //second child launches the movie
         sprintf ( shellCmd,  "cat %s | %s %s 2>&1 > /dev/null", PIPEPATH, VIDEOPLAYER,  VIDEOPATH );
         if ( system ( shellCmd ) == -1 )
         
            printf ( "Error: %s\n", shellCmd );
            fflush(stdout);
         
      
      else
      
         if ( ( pid = fork () ) < 0 )
         
            perror ( "Fork Failed\n" );
            return -1;
         
         else if ( pid == 0 )
          //third child waits 5 seconds then quits movie
            time1.tv_sec = 5L; //sleep for 5 seconds
            time1.tv_nsec = 0L; //Dont worry about milli seconds

            nanosleep ( &time1, &time2 );

            printf ( "Sleep over, quiting movie\n");
            fflush(stdout);

            fd = open ( PIPEPATH, O_WRONLY );
            write ( fd, MESSAGE, sizeof ( MESSAGE ) );
            close ( fd );
         
      
   

   //Note the first child will never exit as it is a blocking shell script
   while ( ( wpid = wait ( &status ) ) > 0 )
   
      printf ( "Exit status of %d was %d (%s)\n", ( int ) wpid, status, ( status == 0 ) ? "accept" : "reject" );
      fflush(stdout);
   

   unlink ( PIPEPATH );

   return 0;

正如我所指出的,程序永远不会退出,所以只需发出 Ctrl+C

编辑 3:

好吧,我正在进步,看来你给管道的东西和你得到的东西不是一回事,运行这个脚本并观察:

#include <stdio.h>
#include <fcntl.h>
#define PIPE_PATH "testpipe"

int main( int argc, char *argv[] ) 
   int fd;
   FILE *fp;
   char c;

   if ( atoi ( argv [ 1 ] ) == 1 )
   
      printf ("Writer [%s]\n", argv[1]);

      mkfifo ( PIPE_PATH, 0666 );

      fd = open ( PIPE_PATH, O_WRONLY );
      c = getchar();
      write(fd, c, 1);

      close(fd);
   
   else if ( atoi ( argv [ 1 ] ) == 2 )
   
      printf ( "Reader [%s]\n", argv[1] );

      fp = fopen( PIPE_PATH, "r" );
      c = getc ( fp );
      putchar ( c );

      printf ( "\n" );
      fclose ( fp );

      unlink( PIPE_PATH );
   

   return 0;

编辑 4:

J.F. Sebastian 问了一些很好的问题,这是对这些问题的回应。我最终想要做的是同步 omxplayer 的 2 个或更多实例在 2 个或更多树莓派上播放同一部电影。 omxplayer-sync 试图实现这一点,但它不准确,它是用 Python 编写的,不适合这个,它的方法是,IMO,不是一个好的方法。我正在沿着食物链往上走,尝试最简单的解决方案,看看它们是否可行。从最简单到最难,这是我迄今为止尝试过的(或计划尝试)

    在未来约定的时间同时从 shell 启动 omxplayer 实例:FAIL 在未来约定的时间同时从 Python 脚本启动 omxplayer 实例(时间更准确):失败 在未来约定的时间同时从 C 程序启动 omxplayer 实例(时间更准确):失败 启动并立即暂停,然后在未来约定的时间同时从 Python 脚本中取消暂停 omxplayer 实例(更准确的时间):失败 启动并立即暂停,然后取消暂停(通过 echo -n p &gt; namedpipeomxplayer 来自 C 程序的实例(更准确的时间)在未来的约定时间同时:失败 启动并立即暂停,然后取消暂停(通过 write ( fd_of_named_pipe, 'p', sizeof ( 'p') );omxplayer 来自 C 程序的实例(更准确的时间)在未来约定的时间同时:PENDING 重新实现 omxplayer 并调整播放速度以赶上最快的玩家:未来

基本上在我投入大量时间来理解然后修改omxplayer的源代码之前(7)我想看看使用C的本机write ( fd_of_named_pipe, 'p', sizeof ( 'p') )操作是否比它的system(echo -n p &gt; namedpipe)调用更快child 并调用 shell 写入命名管道(我的直觉告诉我它会更快,希望更准确)。如果这可行并且在 15 毫秒内取消暂停所有实例,那太棒了,我永远不必查看 omxpleyer 的源代码。如果没有,作为最后的手段,我将开始修改源代码。

【问题讨论】:

虽然这不是这里的主要问题,你需要检查你的函数调用是否有错误。如果mkfifoopenwrite 中的任何一个失败,则不会在您不注意的情况下写入任何内容。 @Floris 我假设有人删除了他们的评论,b/c 你的第一条评论似乎有点奇怪 @puk - 是的,我正在做一些清理工作,想让 glglgl 有机会看到我的最后评论。 您的最后一次编辑使这个问题变得更有趣和更好(如果完全不同的话):如何通过模拟同时按下一个键来同步不同机器上的两个进程。你有关于“同一时间”的规范吗? 15 毫秒是你的极限吗? 刚刚找到***.com/questions/19853557/… - 一个非常相关的早期问题,提供了很多背景知识。看来您不仅要同步开始信号……对吗? 【参考方案1】:

使用专门用于读取标准输入的程序可能会很有用。

echo 不这样做,但它会打印其命令行参数。那是有区别的。

试试cat

【讨论】:

问题是我需要它来使用 echo。我会修改问题【参考方案2】:

你可以使用

xargs < testpipe echo

这会将testpipe 的输出(一次一行)作为参数传递给echo - 并生成

We are not alone

到屏幕上...

注意 - 您的评论“我想修改 C 代码以使用 echo”,而不考虑 echo 的 coes (特别是它不与管道交互),似乎是倒退的。您真正的问题应该是“我如何将命令从 C 获取到 omxplayer”?

我找到了以下代码 sn-p 用于 omxplayer 控制here

mkfifo /tmp/cmd

omxplayer -ohdmi mymedia.avi < /tmp/cmd

echo . > /tmp/cmd (Start omxplayer running as the command will initial wait for input via the fifo)

echo -n p > /tmp/cmd - Playback is paused

echo -n q > /tmp/cmd - Playback quits

这向我表明以下内容应该适合您:

omxplayer -ohdmi mymedia.avi < testpipe

您的 C 程序根据需要生成字符以供 omxplayer 使用。只是不要关闭管道,保持 C 程序运行。但是,当我尝试编写一个小测试程序来确认这一点时:

#include <stdio.h>
#include <fcntl.h>
#define PATH "testpipe"

int main(void) 
    int fd;
    char c;

    mkfifo(PATH, 0666);
    fd = open( PATH, O_WRONLY);
    char keyStroke[2] = " ";

    while((c = getchar())!='q') 
        keyStroke[0] = c;
        write(fd, keyStroke, 1);
    

    close(fd);
    unlink(PATH);
    return 0;

我发现直到我在发送终端点击“q”之前,上述内容没有得到任何响应——换句话说,直到管道关闭。稍微搜索一下我到https://***.com/a/4060641/1967396,他们建议了一个较低级别的“管道阅读程序”,所以我实现了:

#include <stdio.h>
#define PIPE "testpipe"

int main(void) 
    FILE *fp;
    fp = fopen(PIPE, "r");
    int c;

    while((c = getc(fp)) != EOF)
    
        printf("%c", c);
    

    fclose(fp);
    return 0;

在一个终端中运行此程序,在另一个终端中运行早期程序,我现在可以看到来自一个终端的字符出现在另一个终端中。我认为这就是您想要的(尽管我仍然必须在输入终端的每个字符后按“enter”,因为我使用的是getchar(),但我相信您可以弄清楚如何解决这个问题)。

如果这有帮助,请告诉我...

【讨论】:

一件事,我不认为-ohdmi 是必要的,它只是重定向音频 我发现您的最后两个示例令人困惑,例如最后一个甚至没有创建管道。一个写入管道,另一个读取? 是的,最后一对一起工作 - 写入管道,从管道读取(并回显到屏幕)。将第二个的输出作为omxplayer 的输入可能是您需要的解决方案 - 但我不得不对您真正想要的内容进行大量猜测...... 我认为你犯了很多错误。例如,你为什么写Keystroke,而不是c,还有,你正在关闭fd,但你从未打开它。 你是对的。我在没有互联网访问的计算机上写了这个,并且不得不将代码重新输入到另一台计算机的浏览器中才能回答。我犯了很多错误——我的错。我的原始代码运行良好。我已经编辑了代码 - 现在运行正常。【参考方案3】:

以下是您可以模拟 sleep 10; echo -n p ; | omxplayer 命令的方法:

/* for clock_nanosleep() */
#if ! defined(_POSIX_C_SOURCE)  || _POSIX_C_SOURCE < 200112L
#define _POSIX_C_SOURCE 200112L
#endif

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <unistd.h>

#define Close(FD) do                                           \
    const int Close_fd = (FD);                                  \
    if (close(Close_fd) == -1)                                  \
      fprintf(stderr, "%s:%d: close(" #FD ") %d: %s\n",         \
              __FILE__, __LINE__, Close_fd, strerror(errno));   \
  while(0)

static void _report_error_and_exit(const char* msg) 
  perror(msg);
  _exit(EXIT_FAILURE);


static void report_error_and_exit(const char* msg) 
  perror(msg);
  exit(EXIT_FAILURE);


int main(void) 
  int fd[2]; /* pipe */
  pid_t pid = -1;

  if (pipe(fd) == -1)
    report_error_and_exit("pipe");

  if ((pid = fork()) == -1)
    report_error_and_exit("fork");
  else if (pid == 0)   /* child: sleep, write */
    Close(fd[0]); /* close unused read end of the pipe */

     /* sleep until specified time */
      struct timespec endtime = 0;
      int r = -1;

      /* set some time into the future (just as an example) */
      if (clock_gettime(CLOCK_REALTIME, &endtime) < 0)
        _report_error_and_exit("clock_gettime");
      endtime.tv_sec += 10; /* seconds */

      /* NOTE: use settable system-wide real-time clock */
      while ((r = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME,
                                  &endtime, NULL)) == EINTR)
        ; /* retry */

      if (r != 0)
        _report_error_and_exit("clock_nanosleep");
    

     /* write to the pipe */
      char c = 'p';
      ssize_t size = sizeof c, n = size, m = 0;
      while (n > 0)  /* until there is something to write */
        if ((m = write(fd[1], &c + (size - n), n)) > 0)
          n -= m;
        else if (errno != EINTR)
          _report_error_and_exit("write");
      
    
    _exit(EXIT_SUCCESS); /* child done */
  

  /* parent: run `omxplayer < fd[0]` */
  Close(fd[1]); /* close unused write end of the pipe */

  /* redirect stdin */
  if (fd[0] != STDIN_FILENO) 
    if (dup2(fd[0], STDIN_FILENO) == -1)
      report_error_and_exit("dup2");
    else
      Close(fd[0]);
  
  execlp("omxplayer", "omxplayer", NULL);
  _report_error_and_exit("execlp");

要试一试,请运行:

$ gcc *.c -lrt && ./a.out

它应该立即启动 omxplayer 并在当前时间加上 10 秒后写入 p。它被编写为使用绝对时间进行睡眠。

它应该可以帮助您勾选omxplayer-sync 列表中的下一项。但它不会解决在不同主机上的多个视频播放器上同步播放的问题。

【讨论】:

@puk:顺便说一句,为什么你认为python不适合start the player, wait, unpause it with milliseconds accuracy? 我进行了一些测试,发现 Python 有明显的声音延迟,但 C 没有。我得出结论,C 的计时比 Python 的更准确 您听到the script I provided 有明显的延迟吗? 如果我没记错的话,这是完美运行的版本。当我意识到大约 30 分钟后会有明显的漂移时,我放弃了它。我本来打算保留这个脚本,同时启动客户端,然后使用 tcp 持续同步,但是,正如我在其他地方向您提到的,设置 tcp 连接实际上比 fork 和管道更容易且更不容易出错

以上是关于无法使用 C 中的命名管道与 shell 脚本通信的主要内容,如果未能解决你的问题,请参考以下文章

使用 php 中的名称管道

shell 命名管道,进程间通信, ncat作http server

在linux下可以用命名管道实现c程序与qt的数据通信吗?

如何避免并行命名管道的死锁?

C(Linux)中的管道问题

C# 和 python 之间的命名管道