【发布时间】:2016-06-19 02:51:45
【问题描述】:
我需要阅读最初基于 Linux 的 Cygwin 程序的冗长命令行输出。它在cmd.exe 下工作得很好,每隔几秒打印一次新行。
当我使用下面的这段代码时,ReadFile 函数在该程序停止之前不会返回。然后所有输出由ReadFile 提供并打印出来。
如何使ReadFile 在可用时立即读取该输出?
MSDN 说 ReadFile 直到在 ENABLE_LINE_INPUT 模式下达到 CR 或缓冲区已满时才会返回。该程序使用 Linux 换行符 LF,而不是 Windows CRLF。我使用了 32 字节的小缓冲区并禁用了 ENABLE_LINE_INPUT(顺便说一句,禁用它的正确方法是什么?)。
也许ReadFile 没有返回是因为 Cygwin 程序本身的一些其他问题,而不仅仅是 LF 换行符?但它在 Windows cmd.exe 中运行良好,为什么在 Delphi 控制台应用程序中不行?
const
CommandExe:string = 'iperf3.exe ';
CommandLine:string = '-c 192.168.1.11 -u -b 1m -t 8 -p 5001 -l 8k -f m -i 2';
WorkDir:string = 'D:\PAS\iperf3\win32';// no trailing \
var
SA: TSecurityAttributes;
SI: TStartupInfo;
PI: TProcessInformation;
StdOutPipeRead, StdOutPipeWrite: THandle;
WasOK,CreateOk: Boolean;
Buffer: array[0..255] of AnsiChar;// 31 is Ok
BytesRead: Cardinal;
Line:ansistring;
try// except
with SA do begin
nLength := SizeOf(SA);
bInheritHandle := True;
lpSecurityDescriptor := nil;
end;
CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0);
try
with SI do
begin
FillChar(SI, SizeOf(SI), 0);
cb := SizeOf(SI);
dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
wShowWindow := SW_HIDE;
hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin
hStdOutput := StdOutPipeWrite;
hStdError := StdOutPipeWrite;
end;
Writeln(WorkDir+'\'+CommandExe+' ' + CommandLine);
CreateOk := CreateProcess(nil, PChar(WideString(WorkDir+'\'+CommandExe+' ' + CommandLine)),
@SA, @SA, True,// nil, nil,
CREATE_SUSPENDED or CREATE_NEW_PROCESS_GROUP or NORMAL_PRIORITY_CLASS or CREATE_DEFAULT_ERROR_MODE,// 0,
nil,
PChar(WideString(WorkDir)), SI, PI);
CloseHandle(StdOutPipeWrite);// must be closed here otherwise ReadLn further doesn't work
ResumeThread(PI.hThread);
if CreateOk then
try// finally
repeat
WasOK := ReadFile(StdOutPipeRead, Buffer, SizeOf(Buffer), BytesRead, nil);
if BytesRead > 0 then
begin
Buffer[BytesRead] := #0;
Line := Line + Buffer;
Writeln(Line);
end;
until not WasOK or (BytesRead = 0);
ReadLn;
WaitForSingleObject(PI.hProcess, INFINITE);
finally
CloseHandle(PI.hThread);
CloseHandle(PI.hProcess);
end;
finally
CloseHandle(StdOutPipeRead);
end;
except
on E: Exception do
Writeln('Exception '+E.ClassName, ': ', E.Message);
end;
另外:为什么我们必须在 CreateProcess 之后立即关闭这个句柄?用于读取程序输出:
CloseHandle(StdOutPipeWrite);
如果我在程序结束时关闭它,程序输出是好的,但 ReadLn 永远不会被读取以停止程序。
如何测试所有这些: 在一个命令窗口中启动 iperf3 服务器并让它监听:
D:\PAS\iperf3\win32>iperf3.exe -s -i 2 -p 5001
-----------------------------------------------------------
Server listening on 5001
-----------------------------------------------------------
在另一个命令窗口中启动客户端,它会立即连接到服务器并开始每 2 秒打印一次输出:
D:\PAS\iperf3\win32>iperf3.exe -c 192.168.1.11 -u -b 1m -t 8 -p 5001 -l 8k -f m -i 2
Connecting to host 192.168.1.11, port 5001
[ 4] local 192.168.1.11 port 52000 connected to 192.168.1.11 port 5001
[ ID] Interval Transfer Bandwidth Total Datagrams
[ 4] 0.00-2.00 sec 240 KBytes 0.98 Mbits/sec 30
[ 4] 2.00-4.00 sec 240 KBytes 0.98 Mbits/sec 30
[ 4] 4.00-6.00 sec 248 KBytes 1.02 Mbits/sec 31
[ 4] 6.00-8.00 sec 240 KBytes 0.98 Mbits/sec 30
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams
[ 4] 0.00-8.00 sec 968 KBytes 0.99 Mbits/sec 0.074 ms 0/121 (0%)
[ 4] Sent 121 datagrams
iperf Done.
服务器与客户端一起打印输出:
Accepted connection from 192.168.1.11, port 36719
[ 5] local 192.168.1.11 port 5001 connected to 192.168.1.11 port 52000
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams
[ 5] 0.00-2.00 sec 240 KBytes 983 Kbits/sec 0.052 ms 0/30 (0%)
[ 5] 2.00-4.00 sec 240 KBytes 983 Kbits/sec 0.072 ms 0/30 (0%)
[ 5] 4.00-6.00 sec 248 KBytes 1.02 Mbits/sec 0.077 ms 0/31 (0%)
[ 5] 6.00-8.00 sec 240 KBytes 983 Kbits/sec 0.074 ms 0/30 (0%)
[ 5] 8.00-8.00 sec 0.00 Bytes 0.00 bits/sec 0.074 ms 0/0 (nan%)
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams
[ 5] 0.00-8.00 sec 0.00 Bytes 0.00 bits/sec 0.074 ms 0/121 (0%)
-----------------------------------------------------------
Server listening on 5001
-----------------------------------------------------------
所以 iperf3 客户端在命令窗口中运行良好。现在让我们以客户端模式启动“我的”代码,而 iperf3 服务器仍在侦听。服务器接受连接并开始打印输出
Accepted connection from 192.168.1.11, port 36879
[ 5] local 192.168.1.11 port 5001 connected to 192.168.1.11 port 53069
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams
[ 5] 0.00-2.00 sec 240 KBytes 983 Kbits/sec 0.033 ms 0/30 (0%)
[ 5] 2.00-4.00 sec 240 KBytes 983 Kbits/sec 0.125 ms 0/30 (0%)
[ 5] 4.00-6.00 sec 248 KBytes 1.02 Mbits/sec 0.106 ms 0/31 (0%)
[ 5] 6.00-8.00 sec 240 KBytes 983 Kbits/sec 0.109 ms 0/30 (0%)
[ 5] 8.00-8.00 sec 0.00 Bytes 0.00 bits/sec 0.109 ms 0/0 (nan%)
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams
[ 5] 0.00-8.00 sec 0.00 Bytes 0.00 bits/sec 0.109 ms 0/121 (0%)
-----------------------------------------------------------
Server listening on 5001
-----------------------------------------------------------
这意味着 iperf3 客户端是在“我的”代码中启动的,但它没有打印任何东西!只有在客户端完成后,“我的”代码才会打印此输出:
Connecting to host 192.168.1.11, port 5001
[ 4] local 192.168.1.11 port 53069 connected to 192.168.1.11 port 5001
[ ID] Interval Transfer Bandwidth Total Datagrams
[ 4] 0.00-2.00 sec 240 KBytes 0.98 Mbits/sec 30
[ 4] 2.00-4.00 sec 240 KBytes 0.98 Mbits/sec 30
[ 4] 4.00-6.00 sec 248 KBytes 1.02 Mbits/sec 31
[ 4] 6.00-8.00 sec 240 KBytes 0.98 Mbits/sec 30
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams
[ 4] 0.00-8.00 sec 968 KBytes 0.99 Mbits/sec 0.109 ms 0/121 (0%)
[ 4] Sent 121 datagrams
iperf Done.
因此,cygwin 程序输出的行为会有所不同,具体取决于它是在命令窗口中运行还是在 Delphi 控制台应用程序中运行。 是的,我的 'Line' 输出处理代码并不完美,但让我们看看如何让 ReadFile 实时返回,剩下的我会解决。
【问题讨论】:
-
这是粘贴在别处找到的代码时发生的事情之一
-
什么是Cygwin进程?
-
有时,程序会检查它们所连接的输出类型。当它们连接到控制台时,它们会频繁刷新输出(就像每一行一样)。当它们检测到它们连接到其他东西时,例如管道或磁盘文件,它们会缓冲它们的输出(因为似乎没有人可以看到它)。也许程序提供了覆盖默认行为的选项。
-
我在代码之上添加了定义。 Cygwin 程序是 iperf3.exe。请不要建议我重新编译它,我可以这样做。我相信这个问题比较笼统,并希望找到它为每个人都受益。 David Hefferman:我几乎阅读了 SO 上关于管道和 CreateProcess 的所有帖子,包括您的 cmets,并尝试了 4 种解决方案,然后才在这里发帖。 @quasoft:Zarco Gajic 解决方案仅在子程序停止后才提供输出,我在这里问的是来自 DelphiDabbler 的 UweRaabe 解决方案,Kenny 解决方案几乎相同。我将在下面提供有关 iperf3.exe 的更多信息。
-
@Rob Kennedy:如何欺骗 cygwin 程序,让它相信它已连接到控制台?我在原始问题中添加了更多信息。
标签: linux windows delphi cygwin console-application