【问题标题】:What is wrong with this _popen / select example?这个 _popen / select 示例有什么问题?
【发布时间】:2010-09-14 05:55:24
【问题描述】:

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

我现在知道我正在非套接字上尝试套接字操作。或者我的 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;
}

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

【问题讨论】:

    标签: c select popen


    【解决方案1】:

    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() 时重置 fdset。

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

    编辑:

    显然在 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.
        }
    }
    

    【讨论】:

    • 您对选择需要正确设置的第一个参数的评论对于伯克利套接字是正确的。在 Windows 中并非如此。该参数未使用(尽管它仍应正确设置恕我直言!)。 Poseter 没有指定平台,所以我想我会指出不同的平台。
    • 平台确实是Windows。抱歉没有发帖
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:
        1. select()第一个参数是你集合中最大的文件描述符,加1。(即输出+1)

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

        2. 第一个FD_ISSET(...) 应该在fd_set fd 上。

          if (FD_ISSET(filePointer, &fd))

        3. 您的数据流有数据,那么您需要读取该数据流。使用 fgets(...) 或类似方法从数据源中读取数据。

          字符缓冲区[1024]; ... fgets(buf, sizeof(buf) * sizeof(char), 输出);

        【讨论】:

        • 更改反映了您的建议,尽管选择会抛出 -1。
        【解决方案5】:

        据我所知,Windows 匿名管道不能与 select 等非阻塞调用一起使用。因此,虽然您的 _popen 和 select 代码独立看起来不错,但您不能将两者结合在一起。

        Here's a similar thread elsewhere.

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

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

        【讨论】:

        • 好的..我试过你的方法无济于事。您知道我如何实现非阻塞 fgets 吗?
        • 你不能,AFAICT。您最好的选择是简单地使用另一个线程进行读取,并进行正常的阻塞 IO。
        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

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

          我不明白你的例子的目的是什么。如果您只是想生成一个进程并收集它的标准输出,只需执行此操作(直接来自 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");
             }
          }
          

          就是这样。无需选择。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-06-01
            • 1970-01-01
            • 2013-02-03
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多