更新:我更新了代码和问题描述以反映我的更改。
我现在知道我正在尝试对非套接字进行套接字操作。或者我的 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 代码独立看起来不错,但您无法将两者连接在一起。
使用 PIPE_NOWAIT 标志调用 SetNamedPipeHandleState 可能对您有用,但 MSDN 在这个主题上有点神秘。
所以,我认为你需要寻找其他方法来实现这一目标。我建议在单独的线程中进行读取,并使用正常的阻塞 I/O。
首先,正如您自己和其他人所指出的,
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");
}
}
就是这样。无需选择。
而且我不确定线程将如何帮助您,这只会使您的问题复杂化。
我注意到的第一件事是错误的,你在每个条件中的 exceptfds 上调用 FD_ISSET 。我想你想要这样的东西:
if (FD_ISSET(filePointer,&fd))
{
printf("i have data\n");
}
else ....
select 中的 except 字段通常用于报告套接字上的错误或带外数据。当设置异常的描述符之一时,它并不一定意味着错误,而是意味着一些“消息”(即带外数据)。我怀疑对于您的应用程序,您可能不需要将文件描述符放入异常集中即可完成。如果您确实想检查错误,则需要检查 select 的返回值,并在它返回 -1(或 Windows 上的 SOCKET_ERROR)时执行某些操作。我不确定您的平台,因此无法更具体地说明返回代码。
select()
第一个参数是集合中最大编号的文件描述符,加上 1。(即输出+1)
选择(输出+1,&fd,NULL,&exceptfds,NULL);
第一个
FD_ISSET(...)
应该位于 fd_set
fd 上。
if (FD_ISSET(文件指针, &fd))
你的数据流有数据,那么你需要读取该数据流。使用 fgets(...) 或类似的方法从数据源读取。
char buf[1024]; ... fgets(buf, sizeof(buf) * sizeof(char), 输出);
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.
}
}