这个_popen / select示例有什么问题?

时间:2008-09-30 12:26:32

标签: c select popen

更新:我更新了代码和问题说明以反映我的更改。

我现在知道我正在尝试对nonsocket进行Socket操作。或者我的fd_set无效,因为:

select返回-1和 WSAGetLastError()返回10038.

但我似乎无法弄清楚它是什么。平台是Windows。我还没有发布WSAStartup部分。

int loop = 0;
FILE *output

int main()
{
    fd_set fd;
    output = _popen("tail -f test.txt","r");

    while(forceExit == 0)
    {   
        FD_ZERO(&fd);
        FD_SET(_fileno(output),&fd);

        int returncode = select(_fileno(output)+1,&fd,NULL,NULL,NULL);
        if(returncode == 0)
        {
            printf("timed out");
        }
        else if (returncode < 0)
        {
            printf("returncode: %d\n",returncode);
            printf("Last Error: %d\n",WSAGetLastError());
        }
        else
        {
            if(FD_ISSET(_fileno(output),&fd))
            {
                if(fgets(buff, sizeof(buff), output) != NULL )
                {               
                    printf("Output: %s\n", buff);
                }
            }
            else
            {
                printf(".");
            }
        }
        Sleep(500);
    }
    return 0;
}

现在新的结果当然是打印出返回码和最后一个错误。

7 个答案:

答案 0 :(得分:3)

您准备好阅读一些数据,但实际上并没有阅读任何内容。下次轮询描述符时,数据仍然存在。在继续轮询之前排空管道。

答案 1 :(得分:1)

据我所知,Windows匿名管道不能与像select这样的非阻塞调用一起使用。所以,虽然你的_popen和select代码看起来很好,但是你不能把它们加在一起。

Here's a similar thread elsewhere.

使用PIPE_NOWAIT标志调用SetNamedPipeHandleState可能对你有用,但MSDN对这个主题有点神秘。

所以,我认为你需要考虑实现这一目标的其他方法。我建议在单独的线程中进行读取,并使用正常的阻塞I / O.

答案 2 :(得分:1)

首先,正如您和其他人所指出的那样,select()仅对Windows下的套接字有效。 select()不适用于_popen()返回的流。错误10038清楚地表明了这一点。

我不知道你的例子的目的是什么。如果你只是想生成一个进程并收集它的stdout,那就这样做(直接来自MSDN _popen页面):

int main( void )
{

   char   psBuffer[128];
   FILE   *pPipe;

   if( (pPipe = _popen("tail -f test.txt", "rt" )) == NULL )
      exit( 1 );

   /* Read pipe until end of file, or an error occurs. */

   while(fgets(psBuffer, 128, pPipe))
   {
      printf(psBuffer);
   }


   /* Close pipe and print return value of pPipe. */
   if (feof( pPipe))
   {
     printf( "\nProcess returned %d\n", _pclose( pPipe ) );
   }
   else
   {
     printf( "Error: Failed to read the pipe to the end.\n");
   }
}

就是这样。无需选择。

而且我不确定线程​​如何帮助你,这只会使你的问题复杂化。

答案 3 :(得分:0)

select的第一个参数需要是三个集合中任意一个中编号最大的文件描述符,加上1:

   int select(int nfds, fd_set *readfds, fd_set *writefds,
              fd_set *exceptfds, struct timeval *timeout);

此外:

    if(FD_ISSET(filePointer,&exceptfds))
    {
            printf("i have data\n");
    }

应该是:

    if(FD_ISSET(filePointer,&fd))
    {
            printf("i have data\n");
    }

您应该从select()中检查返回码。

每次调用select()时,您还需要重置fdsets。

您不需要超时,因为您没有使用它。

编辑:

显然在Windows上,nfds被忽略,但应该正确设置,这样代码就更容易移植了。

如果要使用超时,则需要将其作为最后一个参数传递给select调用:

// Reset fd, exceptfds, and timeout before each select()...
int result = select(maxFDPlusOne, &fd, NULL, &exceptfds, &timeout);

if (result == 0)
{
    // timeout
}
else if (result < 0)
{
    // error
}
else
{
    // something happened
    if (FD_ISSET(filePointer,&fd))
    {
        // Need to read the data, otherwise you'll get notified each time.
    }
}

答案 4 :(得分:0)

我注意到的第一件事是错误的是你在每个条件中的exceptfds上调用FD_ISSET。我想你想要这样的东西:

if (FD_ISSET(filePointer,&fd))
{
    printf("i have data\n");
}
else ....

select中的except字段通常用于报告套接字上的错误或带外数据。当你的异常的一个描述符被设置时,它并不一定意味着错误,而是一些“消息”(即带外数据)。我怀疑对于您的应用程序,您可能无需将文件描述符放在异常集中。如果你真的想检查错误,你需要检查select的返回值,如果它返回-1(或Windows上的SOCKET_ERROR)则执行某些操作。我不确定你的平台,所以我不能更具体地说明返回代码。

答案 5 :(得分:0)

  1. select()第一个参数是集合中编号最大的文件描述符加1.(即输出+ 1)

    选择(输出+ 1,&amp; fd,NULL和&amp; exceptfds,NULL);

  2. 第一个FD_ISSET(...)应位于fd_set fd。

    if(FD_ISSET(filePointer,&amp; fd))

  3. 您的数据流包含数据,然后您需要读取该数据流。使用fgets(...)或类似方法从数据源中读取。

    char buf [1024]; ... fgets(buf,sizeof(buf)* sizeof(char),output);

答案 6 :(得分:0)

由于select不起作用我使用了线程,特别是_beginthread , _beginthreadex