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

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

我现在知道我正在尝试在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; } 

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

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

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

这是其他地方的类似post。

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

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

首先,正如您和其他人所指出的那样, 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"); } } 

而已。 无需选择。

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

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

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

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

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

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

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

    if(FD_ISSET(filePointer,&fd))

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

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

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. } } 

因为select不起作用我使用了线程,特别是_beginthread_beginthreadex