K-m*_*man 5 linux windows delphi cygwin console-application
我需要阅读最初基于 Linux 的 Cygwin 程序的冗长命令行输出。它在 下工作得很好cmd.exe,每隔几秒打印新行。
当我使用下面的这段代码时(这里已经讨论过很多次了),ReadFile直到该程序停止后,函数才会返回。然后所有输出由 提供ReadFile并打印。
如何使输出一ReadFile可用就被读取?
MSDN 说,直到达到模式或缓冲区已满时ReadFile才会返回。该程序使用 Linux 换行符,而不是 Windows 。我使用了 32 字节的小缓冲区并禁用了(顺便问一下,禁用它的正确方法是什么?)。CRENABLE_LINE_INPUTLFCRLFENABLE_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;
Run Code Online (Sandbox Code Playgroud)
另外:为什么我们必须在 CreateProcess 之后立即关闭这个句柄?它用于读取程序输出:
CloseHandle(StdOutPipeWrite);
Run Code Online (Sandbox Code Playgroud)
如果我在程序结束时关闭它,程序输出正常,但永远不会读取 ReadLn 来停止程序。
如何测试这一切:在一个命令窗口中启动 iperf3 服务器并让它监听:
D:\PAS\iperf3\win32>iperf3.exe -s -i 2 -p 5001
-----------------------------------------------------------
Server listening on 5001
-----------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
在另一个命令窗口中,您启动客户端,它立即连接到服务器并开始每 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.
Run Code Online (Sandbox Code Playgroud)
服务器也与客户端一起打印输出:
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
-----------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
所以 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
-----------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
这意味着 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.
Run Code Online (Sandbox Code Playgroud)
因此,cygwin 程序输出的行为有所不同,具体取决于它是在命令窗口还是 Delphi 控制台应用程序内运行。是的,我的“Line”输出处理代码并不完美,但让我们了解如何使 ReadFile 实时返回,我将修复其余部分。
如何让 ReadFile 在输出可用时立即读取该输出?
问题不在您提供的代码中。它已经在实时读取输出(尽管代码还有另一个不相关的问题,请参见下文)。
您可以使用以下批处理文件而不是 Cygwin 可执行文件来尝试:
测试.bat:
timeout 5
echo "1"
timeout 5
echo "2"
timeout 5
echo "3"
Run Code Online (Sandbox Code Playgroud)
以及以下 bash shell 文件:
测试.sh:
sleep 5
echo "1"
sleep 5
echo "2"
sleep 5
echo "3"
Run Code Online (Sandbox Code Playgroud)
它实时工作,并在文本可用时立即将其输出到控制台。
所以如果问题不在Delphi代码中,则与Cygwin程序有关。我们需要有关您的 Cygwin 计划的更多信息来为您提供进一步帮助。
MSDN 表示,在 ENABLE_LINE_INPUT 模式下达到 CR 或缓冲区已满之前,ReadFile 不会返回。该程序使用 Linux 换行符 LF,而不是 Windows CR LF。我使用了 32 字节的小缓冲区,禁用了 ENABLE_LINE_INPUT - 顺便说一句,禁用它的正确方法是什么?
您不需要禁用它。
如果您将缓冲区设置为 32 字节,那么一旦缓冲区已满,ReadFile函数就应该返回这 32 字节,即使使用 UNIX 行结尾也是如此。
也许 ReadFile 没有返回是因为 cygwin 程序本身的一些其他问题,而不仅仅是 LF 换行符?
我是这么想的。我不想猜测可能的原因,但它们与行结尾的差异无关。
是的,非 Windows 行结尾可以使命令等待整个缓冲区被填充,但不会导致 ReadFile 阻塞。
但它在 Windows cmd.exe 中工作正常,为什么在 Delphi 控制台应用程序中不行呢?
好问题,这很奇怪。就我而言,它在 Delphi 和 cmd 中都可以工作。这就是为什么我认为问题与 Cygwin 应用程序有关。
另外:为什么我们必须在 CreateProcess 之后立即关闭这个句柄?关闭句柄(StdOutPipeWrite);
这是管道的写入端。我们不需要写入句柄,因为我们不写入管道,我们只是从中读取。您的 Cygwin 应用程序正在间接写入该管道。
另外,代码中还有两个问题需要注意:
您有一个Line字符串类型的变量且未初始化。Line := ''在例程/程序的开头将其初始化为空字符串 ( )。
由于您的 UNIX 行以 结尾Buffer,ReadFile因此除非缓冲区已满(因此包含多行),否则不会返回。WriteLn您需要将对例程的调用更改为Write并忽略行结尾,或者使用分隔行的解析器。
Line变量应该在写入后清除stdout,或者应该直接接收 Buffer 的值,如下所示:
...
Buffer[BytesRead] := #0;
Line := Buffer; // <- Assign directly to Line, do not concatenate
// TODO: Use a parser to separate the multiple lines
// in `Line` and output then with `WriteLn` or
// ignore line endings altogether and just use `Write`
Write(Line);
...
Run Code Online (Sandbox Code Playgroud)
除非你这样做,否则 的大小Line将逐渐增加,直到它包含整个输出,并进行复制。
| 归档时间: |
|
| 查看次数: |
1846 次 |
| 最近记录: |