因為QTP的需要,同事寫了通過進程來調用Plink進行Telnet連接的接口。我測試的時候發現,他那個調用.Net 里面的Process進程的方法,通過重定向獲取標準輸出流的辦法有點不好,就是調用了流動Read()函數之后,就會一直阻塞在那里,知道流中有數據才能正確返回,而peek函數又不能正確的監測到流中是否有數據可以讀。我先去翻翻了MSDN中那個StreamReader類的辦法,好像確實沒有辦法,反倒是在Process的StandardOutput屬性的說明那里,明顯寫著,如果標準輸出里面沒有數據的話,read函數就會無限時的阻塞在那里知道有數據可以讀才行,然后他還提到了一些導致死鎖的問題。
我去寫了個簡單的.Net程序來測試了一下,可以知道那個StreamReader是一個FileStream來的,而且那個CanTimeout等屬性都表明不是一個可以異步讀取的流。難道真沒有辦法監測到這個流中是否有數據可讀的狀態嗎? 根據常識知道,這個流應該是“匿名管道”來的,去找了一下MSDN中關于管道的api函數,還真讓我找到了一個,那就是PeekNamedPipe http://msdn.microsoft.com/en-us/library/aa365779(VS.85).aspx。
根據它的說明,來看看這個
The PeekNamedPipe function is similar to the ReadFile function with the following exceptions:
- The data is read in the mode specified with CreateNamedPipe. For example, create a pipe with PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE. If you change the mode to PIPE_READMODE_BYTE with SetNamedPipeHandleState, ReadFile will read in byte mode, but PeekNamedPipe will continue to read in message mode.
- The data read from the pipe is not removed from the pipe's buffer.
- The function can return additional information about the contents of the pipe.
- The function always returns immediately in a single-threaded application, even if there is no data in the pipe. The wait mode of a named pipe handle (blocking or nonblocking) has no effect on the function.
Note The PeekNamedPipe function can block thread execution the same way any I/O function can when called on a synchronous handle in a multi-threaded application. To avoid this condition, use a pipe handle created for asynchronous I/O.
If the specified handle is a named pipe handle in byte-read mode, the function reads all available bytes up to the size specified in nBufferSize. For a named pipe handle in message-read mode, the function reads the next message in the pipe. If the message is larger than nBufferSize, the function returns TRUE after reading the specified number of bytes. In this situation, lpBytesLeftThisMessage will receive the number of bytes remaining in the message.
這個函數不管命名管道是不是阻塞模式的,都會立即返回(除了在多線程環境下的某種情況下會阻塞,大概就是http://my.donews.com/yeyanbo/tag/peeknamedpipe/這個文章發現的問題。),文檔又說所有的”匿名管道“其實都是一個“命名管道”來實現的,所以操作“命名管道”的函數對“匿名管道”也是有效的。這個函數明顯是我想要的,可以用來檢測process標準輸出流中是否有數據可以讀,又不會阻塞。在vb.net的測試代碼里面試了一下,應該是可以工作,測試代碼如下:
Imports System.Diagnostics.Process
Public Class Form1
Declare Function SetNamedPipeHandleState Lib "kernel32" (ByVal hNamedPipe As Integer, ByRef lpMode As Integer, ByRef lpMaxCollectionCount As Integer, ByRef lpCollectDataTimeout As Integer) As Integer
Declare Function PeekNamedPipe Lib "kernel32" (ByVal hNamedPipe As Integer, ByRef lpBuffer As Integer, ByVal nBufferSize As Integer, ByRef lpBytesRead As Integer, ByRef lpTotalBytesAvail As Integer, ByRef lpBytesLeftThisMessage As Integer) As Integer
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim p As Process = New Process
p.StartInfo.FileName = "c:\windows\system32\cmd.exe"
p.StartInfo.RedirectStandardInput = True
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.UseShellExecute = False
p.Start()
p.StandardInput.WriteLine("hostname")
Dim f As System.IO.FileStream = p.StandardOutput.BaseStream
Dim mode As Integer
mode = 1 ' no-wait
Dim count As Integer
'修改這個命名管道為 異步的,是不能成功的
''mode = SetNamedPipeHandleState(f.Handle, mode, System.IntPtr.Zero, System.IntPtr.Zero)
'不過這個PeekNamePipe 函數是可以得到 管道里面有多少字節可以讀到,執行完之后count里面是對的,可以讀取的數據
mode = PeekNamedPipe(f.Handle, System.IntPtr.Zero, 0, System.IntPtr.Zero, count, System.IntPtr.Zero)
p.StandardOutput.Read()
While p.StandardOutput.Peek > 0
p.StandardOutput.Read()
End While
'在這個地方的時候就不能再read了,read就無限阻塞直到有數據來才能返回了。
mode = p.StandardOutput.Peek() '這個時候的peek返回 -1是對的
mode = PeekNamedPipe(f.Handle, System.IntPtr.Zero, 0, System.IntPtr.Zero, count, System.IntPtr.Zero) '這個count得到0 是對的,管道里面沒有消息的了
p.StandardInput.WriteLine("hostname")
mode = p.StandardOutput.Peek() '這個還是返回 -1是不對的,
mode = PeekNamedPipe(f.Handle, System.IntPtr.Zero, 0, System.IntPtr.Zero, count, System.IntPtr.Zero) '這個返回正確的count,表明管道里面有數據是對的
p.StandardOutput.Read()
p.StandardOutput.Peek() 'peek函數一定要在read成功調用過一次之后才能正確的得到管道的狀態。但Read一次又可能引起無限時間的阻塞!!!!所以只有PeekNamedPipe才能正確的無阻塞的檢測到管道的數據
End Sub
End Class
總結一下 :感覺。Net對這個“命名管道“”匿名管道“的支持明顯不夠,API中都有監測到管道是否有數據可以讀到函數。.Net里面卻連管道對應的類都沒有實現,所以相應的這種阻塞情況就也沒法處理了。可能這部分的封裝有待完善吧。
后來有用Reflector工具反匯編看了看系統Process幾個類的相應實現代碼,可以看到他是CreatePipe創建一個管道,然后DuplicateHandle復制了一個文件句柄的,跟我事先的猜測是一樣的。Linux的輸出重定向使用也要類似的這樣兩個步驟。如果你自己看他的代碼,可以看到創建的是一個只讀的、不支持異步的FileSteam來的,他代碼是這樣寫的:
public bool Start()
{
this.Close();
ProcessStartInfo startInfo = this.StartInfo;
if (startInfo.FileName.Length == 0)
{
throw new InvalidOperationException(SR.GetString("FileNameMissing"));
}
if (startInfo.UseShellExecute)
{
return this.StartWithShellExecuteEx(startInfo);
}
return this.StartWithCreateProcess(startInfo);
}
private bool StartWithCreateProcess(ProcessStartInfo startInfo)
{
if ((startInfo.StandardOutputEncoding != null) && !startInfo.RedirectStandardOutput)
{
throw new InvalidOperationException(SR.GetString("StandardOutputEncodingNotAllowed"));
}
if ((startInfo.StandardErrorEncoding != null) && !startInfo.RedirectStandardError)
{
throw new InvalidOperationException(SR.GetString("StandardErrorEncodingNotAllowed"));
}
if (this.disposed)
{
throw new ObjectDisposedException(base.GetType().Name);
}
StringBuilder cmdLine = BuildCommandLine(startInfo.FileName, startInfo.Arguments);
NativeMethods.STARTUPINFO lpStartupInfo = new NativeMethods.STARTUPINFO();
SafeNativeMethods.PROCESS_INFORMATION lpProcessInformation = new SafeNativeMethods.PROCESS_INFORMATION();
SafeProcessHandle processHandle = new SafeProcessHandle();
SafeThreadHandle handle2 = new SafeThreadHandle();
int error = 0;
SafeFileHandle parentHandle = null;
SafeFileHandle handle4 = null;
SafeFileHandle handle5 = null;
GCHandle handle6 = new GCHandle();
try
{
bool flag;
if ((startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput) || startInfo.RedirectStandardError)
{
if (startInfo.RedirectStandardInput)
{
this.CreatePipe(out parentHandle, out lpStartupInfo.hStdInput, true);
}
else
{
lpStartupInfo.hStdInput = new SafeFileHandle(NativeMethods.GetStdHandle(-10), false);
}
if (startInfo.RedirectStandardOutput)
{
this.CreatePipe(out handle4, out lpStartupInfo.hStdOutput, false);
}
else
{
lpStartupInfo.hStdOutput = new SafeFileHandle(NativeMethods.GetStdHandle(-11), false);
}
中間省略一部分
if (startInfo.RedirectStandardInput)
{
this.standardInput = new StreamWriter(new FileStream(parentHandle, FileAccess.Write, 0x1000, false), Encoding.GetEncoding(NativeMethods.GetConsoleCP()), 0x1000);
this.standardInput.AutoFlush = true;
}
if (startInfo.RedirectStandardOutput)
{
Encoding encoding = (startInfo.StandardOutputEncoding != null) ? startInfo.StandardOutputEncoding : Encoding.GetEncoding(NativeMethods.GetConsoleOutputCP());
this.standardOutput = new StreamReader(new FileStream(handle4, FileAccess.Read, 0x1000, false), encoding, true, 0x1000);
}
if (startInfo.RedirectStandardError)
{
Encoding encoding2 = (startInfo.StandardErrorEncoding != null) ? startInfo.StandardErrorEncoding : Encoding.GetEncoding(NativeMethods.GetConsoleOutputCP());
this.standardError = new StreamReader(new FileStream(handle5, FileAccess.Read, 0x1000, false), encoding2, true, 0x1000);
}
bool flag3 = false;
if (!processHandle.IsInvalid)
{
this.SetProcessHandle(processHandle);
this.SetProcessId(lpProcessInformation.dwProcessId);
handle2.Close();
flag3 = true;
}
return flag3;
}
private void CreatePipe(out SafeFileHandle parentHandle, out SafeFileHandle childHandle, bool parentInputs)
{
NativeMethods.SECURITY_ATTRIBUTES lpPipeAttributes = new NativeMethods.SECURITY_ATTRIBUTES();
lpPipeAttributes.bInheritHandle = true;
SafeFileHandle hWritePipe = null;
try
{
if (parentInputs)
{
CreatePipeWithSecurityAttributes(out childHandle, out hWritePipe, lpPipeAttributes, 0);
}
else
{
CreatePipeWithSecurityAttributes(out hWritePipe, out childHandle, lpPipeAttributes, 0);
}
if (!NativeMethods.DuplicateHandle(new HandleRef(this, NativeMethods.GetCurrentProcess()), hWritePipe, new HandleRef(this, NativeMethods.GetCurrentProcess()), out parentHandle, 0, false, 2))
{
throw new Win32Exception();
}
}
finally
{
if ((hWritePipe != null) && !hWritePipe.IsInvalid)
{
hWritePipe.Close();
}
}
}
private static void CreatePipeWithSecurityAttributes(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, NativeMethods.SECURITY_ATTRIBUTES lpPipeAttributes, int nSize)
{
if ((!NativeMethods.CreatePipe(out hReadPipe, out hWritePipe, lpPipeAttributes, nSize) || hReadPipe.IsInvalid) || hWritePipe.IsInvalid)
{
throw new Win32Exception();
}
}
public override int Peek()
{
if (this.stream == null)
{
__Error.ReaderClosed();
}
if ((this.charPos != this.charLen) || (!this._isBlocked && (this.ReadBuffer() != 0)))
{
return this.charBuffer[this.charPos];
}
return -1;
}
轉自:
http://hi.baidu.com/widebright/item/f58e2516a6bb41dcbf9042a4