因?yàn)镼TP的需要,同事寫了通過進(jìn)程來調(diào)用Plink進(jìn)行Telnet連接的接口。我測試的時(shí)候發(fā)現(xiàn),他那個(gè)調(diào)用.Net 里面的Process進(jìn)程的方法,通過重定向獲取標(biāo)準(zhǔn)輸出流的辦法有點(diǎn)不好,就是調(diào)用了流動(dòng)Read()函數(shù)之后,就會(huì)一直阻塞在那里,知道流中有數(shù)據(jù)才能正確返回,而peek函數(shù)又不能正確的監(jiān)測到流中是否有數(shù)據(jù)可以讀。我先去翻翻了MSDN中那個(gè)StreamReader類的辦法,好像確實(shí)沒有辦法,反倒是在Process的StandardOutput屬性的說明那里,明顯寫著,如果標(biāo)準(zhǔn)輸出里面沒有數(shù)據(jù)的話,read函數(shù)就會(huì)無限時(shí)的阻塞在那里知道有數(shù)據(jù)可以讀才行,然后他還提到了一些導(dǎo)致死鎖的問題。
我去寫了個(gè)簡單的.Net程序來測試了一下,可以知道那個(gè)StreamReader是一個(gè)FileStream來的,而且那個(gè)CanTimeout等屬性都表明不是一個(gè)可以異步讀取的流。難道真沒有辦法監(jiān)測到這個(gè)流中是否有數(shù)據(jù)可讀的狀態(tài)嗎? 根據(jù)常識(shí)知道,這個(gè)流應(yīng)該是“匿名管道”來的,去找了一下MSDN中關(guān)于管道的api函數(shù),還真讓我找到了一個(gè),那就是PeekNamedPipe http://msdn.microsoft.com/en-us/library/aa365779(VS.85).aspx。
根據(jù)它的說明,來看看這個(gè)
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.
這個(gè)函數(shù)不管命名管道是不是阻塞模式的,都會(huì)立即返回(除了在多線程環(huán)境下的某種情況下會(huì)阻塞,大概就是http://my.donews.com/yeyanbo/tag/peeknamedpipe/這個(gè)文章發(fā)現(xiàn)的問題。),文檔又說所有的”匿名管道“其實(shí)都是一個(gè)“命名管道”來實(shí)現(xiàn)的,所以操作“命名管道”的函數(shù)對“匿名管道”也是有效的。這個(gè)函數(shù)明顯是我想要的,可以用來檢測process標(biāo)準(zhǔn)輸出流中是否有數(shù)據(jù)可以讀,又不會(huì)阻塞。在vb.net的測試代碼里面試了一下,應(yīng)該是可以工作,測試代碼如下:
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
'修改這個(gè)命名管道為 異步的,是不能成功的
''mode = SetNamedPipeHandleState(f.Handle, mode, System.IntPtr.Zero, System.IntPtr.Zero)
'不過這個(gè)PeekNamePipe 函數(shù)是可以得到 管道里面有多少字節(jié)可以讀到,執(zhí)行完之后count里面是對的,可以讀取的數(shù)據(jù)
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
'在這個(gè)地方的時(shí)候就不能再read了,read就無限阻塞直到有數(shù)據(jù)來才能返回了。
mode = p.StandardOutput.Peek() '這個(gè)時(shí)候的peek返回 -1是對的
mode = PeekNamedPipe(f.Handle, System.IntPtr.Zero, 0, System.IntPtr.Zero, count, System.IntPtr.Zero) '這個(gè)count得到0 是對的,管道里面沒有消息的了
p.StandardInput.WriteLine("hostname")
mode = p.StandardOutput.Peek() '這個(gè)還是返回 -1是不對的,
mode = PeekNamedPipe(f.Handle, System.IntPtr.Zero, 0, System.IntPtr.Zero, count, System.IntPtr.Zero) '這個(gè)返回正確的count,表明管道里面有數(shù)據(jù)是對的
p.StandardOutput.Read()
p.StandardOutput.Peek() 'peek函數(shù)一定要在read成功調(diào)用過一次之后才能正確的得到管道的狀態(tài)。但Read一次又可能引起無限時(shí)間的阻塞!!!!所以只有PeekNamedPipe才能正確的無阻塞的檢測到管道的數(shù)據(jù)
End Sub
End Class
總結(jié)一下 :感覺。Net對這個(gè)“命名管道“”匿名管道“的支持明顯不夠,API中都有監(jiān)測到管道是否有數(shù)據(jù)可以讀到函數(shù)。.Net里面卻連管道對應(yīng)的類都沒有實(shí)現(xiàn),所以相應(yīng)的這種阻塞情況就也沒法處理了。可能這部分的封裝有待完善吧。
后來有用Reflector工具反匯編看了看系統(tǒng)Process幾個(gè)類的相應(yīng)實(shí)現(xiàn)代碼,可以看到他是CreatePipe創(chuàng)建一個(gè)管道,然后DuplicateHandle復(fù)制了一個(gè)文件句柄的,跟我事先的猜測是一樣的。Linux的輸出重定向使用也要類似的這樣兩個(gè)步驟。如果你自己看他的代碼,可以看到創(chuàng)建的是一個(gè)只讀的、不支持異步的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;
}
轉(zhuǎn)自:
http://hi.baidu.com/widebright/item/f58e2516a6bb41dcbf9042a4