这个_popen / select示例有什么问题?
更新:我更新了代码和问题描述以反映我的更改。
我现在知道我正在尝试在非套接字上执行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; }
现在,新的结果当然是打印出返回码和最后一个错误。
解决方案
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. } }
我注意到的第一件事是错误的,是我们在每个条件中的exceptfds上调用FD_ISSET。我认为我们想要这样的东西:
if (FD_ISSET(filePointer,&fd)) { printf("i have data\n"); } else ....
select中的except字段通常用于报告套接字上的错误或者带外数据。设置了异常的描述符之一后,它并不一定意味着错误,而是一些"消息"(即带外数据)。我怀疑对于应用程序,我们可能无需将文件描述符放入异常集内就可以实现。如果我们确实要检查错误,则需要检查select的返回值并在返回-1(或者Windows上的SOCKET_ERROR)的情况下执行某些操作。我不确定平台,因此我无法更详细地说明返回码。
我们已经准备好读取一些数据,但实际上并没有读取任何内容。下次轮询描述符时,数据仍将存在。在继续轮询之前,先排空管道。
- select()第一个参数是集合中编号最高的文件描述符,加上1. (即output + 1)select(output + 1,&fd,NULL,&exceptfds,NULL);
- 第一个FD_ISSET(...)应该在fd_set` fd上。如果(FD_ISSET(filePointer,&fd))
- 数据流中有数据,那么我们需要读取该数据流。使用fgets(...)或者类似方法从数据源读取。 char buf [1024]; ... fgets(buf,sizeof(buf)* sizeof(char),输出);
据我所知,Windows匿名管道不能与select等非阻塞调用一起使用。因此,尽管_popen和select代码独立看起来不错,但是我们无法将两者结合在一起。
这是其他地方的类似主题。
使用PIPE_NOWAIT标志调用SetNamedPipeHandleState可能对我们有用,但是MSDN在这个问题上有点含糊不清。
因此,我认为我们需要研究实现这一目标的其他方法。我建议在单独的线程中进行读取,并使用常规的阻塞I / O。
因为select
不起作用,所以我使用了线程,特别是_beginthread
和_beginthreadex
。
首先,正如我们和其他人指出的那样,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"); } }
而已。无需选择。
而且我不确定线程如何在这里为我们提供帮助,这只会使问题复杂化。