C# 中使用重叠 I/O 的异步命名管道服务器






3.13/5 (7投票s)
C# 中的异步命名管道服务器。
引言
我最近阅读了 J. Richter 撰写的关于 .NET 2.0 内部机制和由此产生的 C# 特性的 CLR via C#。 我决定练习一下,用 C# 编写我的新应用程序。 我在这里展示的是这个应用程序的一部分。 这是一个使用重叠 I/O 的异步命名管道服务器,以及一个同步(或他们也称之为阻塞)命名管道客户端。
遇到的问题
遇到的第一个问题是 唯一的例子 由 John Korres 用 VB.NET 编写的使用重叠结构的例子为两个 Win32 API 函数 ConnectNamedPipe
和 DisconnectNamedPipe
提供了异步调用。 使用 System.IO.FileStream
.NET 类,以阻塞模式实现对命名管道服务器的读写。 我尝试使用 BeginRead
和 BeginWrite
方法,但仍然不清楚这两个方法如何与 NativeOverlapped
结构交互,如果它们确实交互的话。 所以最终,我采用了 这个示例代码,它是用 C 语言编写的,来自 MSDN。
这就是第二个问题出现的原因。 在重写代码之后,我发现 GetOverlappedResult
Win32 API 函数无法正常工作,因为指向重叠结构的指针没有被固定,也就是说,您必须使用进程的非托管内存才能使其像在普通的 C 语言中一样工作。 进一步调查后,我了解到 ReadFile
和 WriteFile
Win32 API 函数的所有缓冲区也必须使用非托管内存。 我决定使用指针并使用 unsafe 选项编译应用程序;这让我有点沮丧,因为我的代码现在看起来几乎像普通的 C 语言。 这还不仅仅是这样。 Byte*
到 IntPtr
的转换也不能正常工作。 所以我改为使用 IntPtr
代替重叠结构和缓冲区,这让我可以删除这个可恶的 unsafe 参数。 我得到了回报。 现在,采用的代码与 MSDN 的 示例 完全一样。
我在这里不得不提到的最后一个但并非最不重要的问题是命名管道服务器的权限。 使用默认权限将不允许两台计算机之间的进程间通信,所以请在 NamedPipeServer.cs 中查找 buildSecurityAttributes
。 我不会在这里赘述,因为它不是本文的重点。
使用代码
以下代码摘录显示了主服务器循环,并暴露了异步编程的特性。 实际的重叠魔法发生在 selectOperation
方法内的 ReadFile
Win32 API 返回 FALSE
并且 GetLastError
返回 ERROR_IO_PENDING
时,这不是一个错误,而是一个信号,表明 ReadFile
异步调用未能完成读取,所以我们必须调用 GetOverlappedResult
Win32 API 来查明操作系统是否已将 cbRet
字节放入 mPipeServer[i].Pipe.chRequest
中。 如果没有,我们调用 DisconnectAndReconnect
方法。
如果应用程序在 Visual Studio 2005 下运行,无论它是发布配置还是调试配置,都会发生另一种情况。 为了真正见证这种差异,请取消注释 MessageBox.Show(“GetOverlappedResult:...")
以及它上面的行,然后运行可执行文件。 之后,在 Visual Studio 中以调试配置运行它。 您可能不会遇到它。
while (mListenning)
{
i = (UInt32)WaitHandle.WaitAny(hEvents);
// determines which pipe instance
if (i < 0 || i > (mNumOfInstances - 1))
{
ThrowWin32Exception ("Index out of range.");
return;
}
Debug.WriteLine("Instance -> " + i.ToString());
#region ERROR_IO_PENDING == TRUE
if (mPipeServer[i].Pipe.fPendingIO)
{
fSuccess = NativeMethods.GetOverlappedResult(
mPipeServer[i].Pipe.hPipeInst,
mPipeServer[i].Pipe.OverlappedPtr,
out cbRet,
false);
//if (cbRet > 0)
// MessageBox.Show("GetOverlappedResult: " +
// Marshal.PtrToStringAnsi(mPipeServer[i].Pipe.chRequest));
switch (mPipeServer[i].Pipe.dwState)
{
// Pending connect operation
case CONNECTING_STATE:
Debug.WriteLine("CONNECTING_STATE");
if (!fSuccess)
ThrowWin32Exception("CONNECTING_STATE_ERROR");
//SetEventAsync(CONNECTING_STATE, mPipeServer[i].Pipe.chRequest,
// mPipeServer[i].Pipe.cbRead);
//STEP #0
mPipeServer[i].Pipe.dwState = READING_STATE;
break;
// Pending read operation
case READING_STATE:
Debug.WriteLine("READING_STATE " + cbRet.ToString());
if (!fSuccess || cbRet == 0)
{
DebugWin32Exception("READING_STATE");
DisconnectAndReconnect(i);
continue;
}
//STEP #1
//NB! If we got here and cbRet > 0
// the mPipeServer[i].Pipe.chRequest already has client data
mPipeServer[i].Pipe.dwState = WRITING_STATE;
break;
// Pending write operation
case WRITING_STATE:
Debug.WriteLine("WRITING_STATE " + cbRet.ToString());
if (!fSuccess || cbRet != mPipeServer[i].Pipe.cbToWrite)
{
DebugWin32Exception("WRITING_STATE");
DisconnectAndReconnect(i);
continue;
}
mPipeServer[i].Pipe.dwState = READING_STATE;
break;
default:
ThrowWin32Exception("runInstance - INVALID PIPE STATE");
break;
}
} //IO_PENDING
#endregion
if (!selectOperation((Int32) i) )
//does ReadFile or WriteFile depending on the pipe instance state
{
// An error occurred; disconnect from the client.
DisconnectAndReconnect(i);
}
}
关注点
在测试应用程序时,发现它在 Visual Studio 2005 下运行时的行为不同。