C# 异步命名管道
如何使用带有 Async 的命名管道

引言
以下技巧展示了如何使用异步方法来实现命名管道客户端/服务器,以便在 .NET 应用程序之间进行通信。
背景
我创建这篇文章是因为我发现很难找到使用异步方法的命名管道客户端/服务器的好例子。我在 CodeProject 上找到了一篇由 "Maddog Mike B" 撰写的精彩文章,但它只适用于 .NET 3.5,所以我必须为 .NET 4.5 设计一个新版本。大多数命名管道示例似乎都是基于同步管道与线程拼接来创建异步管道。我不知道我提供的方法是否正确,但这是经过数小时在网上搜寻并将零星信息拼凑在一起的结果。
概述
此代码已使用 Visual Studio 2012 和 .NET 4.5 进行了测试。该项目分为两个基于 WinForm 的项目,PipeTestClient
(向服务器发送消息)和 PipeTestServer
(侦听来自客户端的传入消息)。这两个项目都以 zip 格式提供。
当方法完成时,将调用 AsyncCallback
方法。 在 .NET 中,事件可以触发委托,但方法也可以触发委托。AsyncCallback
类允许方法启动一个异步函数,并提供一个委托方法,以便在异步函数完成时调用。
状态对象可用于在异步函数调用和相应的 AsyncCallback
方法之间传递信息。
异步侦听方法 [侦听服务器类]
调用 Listen()
方法,该方法接受一个参数 PipeName
,该参数被分配给一个类级别的 var
,以便稍后在递归函数中使用。 使用 PipeOptions.Asynchronous
参数(异步操作所需)创建 NamedPipeServerStream
。 也使用传入的 PipeName
。(必须与客户端的名称相同)。 服务器的异步 BeginWaitFoConnection()
方法使用 AsyncCallback
调用来触发 WaitForConnectionCallback
方法,该方法也接收 state
对象 - 在这种情况下是原始管道。 进入 WaitForConnectionCallback
方法后,将传递的状态管道分配给本地 var
,并结束等待连接。 创建读取缓冲区,并将消息数据读入并转换为 string
类型。 然后终止原始服务器管道,并使用相同的条件和原始管道创建一个新的管道,新的服务器管道开始使用自己的方法作为 AsyncCallback
函数(递归)等待另一个连接。
// Delegate for passing received message back to caller
public delegate void DelegateMessage(string Reply);
class PipeServer
{
public event DelegateMessage PipeMessage;
string _pipeName;
public void Listen(string PipeName)
{
try
{
// Set to class level var so we can re-use in the async callback method
_pipeName = PipeName;
// Create the new async pipe
NamedPipeServerStream pipeServer = new NamedPipeServerStream(PipeName,
PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
// Wait for a connection
pipeServer.BeginWaitForConnection
(new AsyncCallback(WaitForConnectionCallBack), pipeServer);
}
catch (Exception oEX)
{
Debug.WriteLine(oEX.Message);
}
}
private void WaitForConnectionCallBack(IAsyncResult iar)
{
try
{
// Get the pipe
NamedPipeServerStream pipeServer = (NamedPipeServerStream)iar.AsyncState;
// End waiting for the connection
pipeServer.EndWaitForConnection(iar);
byte[] buffer = new byte[255];
// Read the incoming message
pipeServer.Read(buffer, 0, 255);
// Convert byte buffer to string
string stringData = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
Debug.WriteLine(stringData + Environment.NewLine);
// Pass message back to calling form
PipeMessage.Invoke(stringData);
// Kill original sever and create new wait server
pipeServer.Close();
pipeServer = null;
pipeServer = new NamedPipeServerStream(_pipeName, PipeDirection.In,
1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
// Recursively wait for the connection again and again....
pipeServer.BeginWaitForConnection(
new AsyncCallback(WaitForConnectionCallBack), pipeServer);
}
catch
{
return;
}
}
}
异步发送方法 [PipeClient 类]
调用 Send()
方法,并将发送字符串、管道名称和到服务器的连接超时时间(以毫秒为单位)作为参数。 移动通过代码,使用 PipeOptions.Asynchronous
参数创建一个 NamedPipedClientStream
(管道),如果我们想成功发送异步管道消息,这一点至关重要。 我们还使用传入的 PipeName
(这应与服务器使用的名称相同)。 创建完成后,我们尝试连接到服务器,如果省略了超时参数,并且服务器不存在,则 Connect
方法将无限期地等待,因此使用超时(以毫秒为单位)是有意义的。 然后创建一个字节数组,并将发送字符串/消息转换为该字节数组。 此时,假设连接成功,则客户端的异步 BeginWrite()
方法使用 AsyncCallback
调用来触发 AsyncSend
方法,该方法也接收 state
对象 - 在这种情况下是原始管道。 进入 AsyncSend
方法后,将传递的状态管道分配给本地 var
,并结束写入。
class PipeClient
{
public void Send(string SendStr, string PipeName, int TimeOut = 1000)
{
try
{
NamedPipeClientStream pipeStream = new NamedPipeClientStream
(".", PipeName, PipeDirection.Out, PipeOptions.Asynchronous);
// The connect function will indefinitely wait for the pipe to become available
// If that is not acceptable specify a maximum waiting time (in ms)
pipeStream.Connect(TimeOut);
Debug.WriteLine("[Client] Pipe connection established");
byte[] _buffer = Encoding.UTF8.GetBytes(SendStr);
pipeStream.BeginWrite
(_buffer, 0, _buffer.Length, new AsyncCallback(AsyncSend), pipeStream);
}
catch (TimeoutException oEX)
{
Debug.WriteLine(oEX.Message);
}
}
private void AsyncSend(IAsyncResult iar)
{
try
{
// Get the pipe
NamedPipeClientStream pipeStream = (NamedPipeClientStream)iar.AsyncState;
// End the write
pipeStream.EndWrite(iar);
pipeStream.Flush();
pipeStream.Close();
pipeStream.Dispose();
}
catch (Exception oEX)
{
Debug.WriteLine(oEX.Message);
}
}
}
用法
服务器代码还包括事件委托,以便将消息发回调用窗体 - 包括用于跨线程通信的线程安全方法 - 请参见代码。
启动客户端和服务器应用程序,单击服务器应用程序上的“侦听”。 然后在客户端应用程序上根据需要多次单击“发送”。 消息传递是即时且可靠的。
历史
- 版本 1.0
帮助
如果有人有任何使此代码更简洁/更快的技巧,请告诉我。