使用 WCF 和 NetNamedPipeBinding 实现的多对一本地 IPC





5.00/5 (12投票s)
我需要在一个服务器进程和多个客户端进程之间进行通信,这是一个简单的测试工具,用于证明这一点
引言
我使用 WCF 和 NetNamedPipeBinding
在我的服务器进程和多个客户端之间进行 IPC,这些客户端将签入并向服务器注册。
客户端需要能够调用服务器上的方法 - 无论是否有参数和返回值,而服务器也需要能够以类似的方式调用客户端上的方法。
在实际的应用示例中,我没有让客户端在退出前取消注册的方法,因此该操作是在服务器端完成的。
这是一张它运行时的图片。
两个客户端都已经注册 - 你可以在服务器上的列表框中看到它们的 GUID
。
其中一个客户端发送了一条消息,另一个客户端发送了一条匿名消息,然后第一个客户端请求了匿名消息。
服务器向所有已注册的客户端发送了一条广播消息。

背景
WCF 的东西起初看起来很简单,但很快我就发现我不得不浏览几篇文章才能让事情正常工作 - 并且作为我第一次涉足 WCF,它可能不是完全的最佳实践 - 我希望我能从评论中学习,如果它不是!
要理解这一点,你不需要理解关于命名管道的任何细节,除了它们是在你的计算机上本地运行的东西,并且 WCF 位于其之上,隐藏了所有细节。
最重要的是要理解 WCF 通信由两个端点(客户端和服务器)以及一个允许它们之间通信的通道组成。在这种情况下,通道使用命名管道来促进消息的传输。
此外,这种 WCF 类似于 COM - 它不需要对消息对象进行编组或序列化,如果你在服务器上调用一个对象方法,它正在运行托管服务器的进程内部并返回结果 - 你真的不需要担心它是如何发生的 - 好吧,反正我没有。
Using the Code
代码实际上没有用处,除了作为一个工作示例,它不是你将链接到现有应用程序的东西,甚至可能不是一个新应用程序的良好起点,但它就是这样,它允许你观察在几种不同的消息传递场景下发生的事情。
第一部分是客户端开始的消息 - 通常第一条消息将是一个“Register
”消息,它提供一个 Guid
来标识客户端到服务器。我将其标记为 OneWay
,以便 WCF 知道不必等待回复。
[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface IFromClientToServerMessages
{
[OperationContract(IsOneWay = true)]
void Register(Guid clientID);
[OperationContract(IsOneWay = true)]
void DisplayTextOnServer(string text);
[OperationContract(IsOneWay = true)]
void DisplayTextOnServerAsFromThisClient(Guid clientID, string text);
[OperationContract]
string GetLastAnonMessage();
}
接下来是服务器开始并发送给客户端的消息 - 在这种情况下,只有一个 - 一个简单的指令来显示一些文本。
[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface IFromServerToClientMessages
{
[OperationContract(IsOneWay = true)]
void DisplayTextInClient(string text);
}
客户端使用它们的 GUID
作为其 URI 的一部分启动。服务器也大致相同,但只是称为“Server
”(即没有 GUID
)。请注意,我所需要做的就是实现接口并启动对象的服务主机,添加一个服务终结点,然后打开连接。然后它能够接收通道连接。在退出时,应该有一个相应的 Dispose
来关闭它,但我已将其从示例中省略。
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant,
InstanceContextMode = InstanceContextMode.Single)]
public partial class ClientForm : Form, IFromServerToClientMessages
{
Guid _clientID;
ServiceHost _clientHost;
public ClientForm()
{
InitializeComponent();
_clientID = Guid.NewGuid();
_clientHost = new ServiceHost(this);
_clientHost.AddServiceEndpoint((typeof(IFromServerToClientMessages)),
new NetNamedPipeBinding(), "net.pipe:///Client_" +
_clientID.ToString());
_clientHost.Open();
}
从客户端到服务器的基本消息具有以下代码 - 我在其他论坛上阅读的文章暗示在消息之间关闭通道是正确的方法。
public void Register(Guid clientID)
{
using (ChannelFactory<IFromClientToServerMessages> factory =
new ChannelFactory<IFromClientToServerMessages>(new NetNamedPipeBinding(),
new EndpointAddress("net.pipe:///Server")))
{
IFromClientToServerMessages clientToServerChannel = factory.CreateChannel();
try
{
clientToServerChannel.Register(clientID);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
CloseChannel((ICommunicationObject)clientToServerChannel);
}
}
}
private void CloseChannel(ICommunicationObject channel)
{
try
{
channel.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
channel.Abort();
}
}
关注点
关闭通道很重要 - 你可以很容易地让一个示例工作,但很快你就会发现 10 分钟后,你开始遇到超时。然后你开始增加超时或尝试使用可靠会话,但是 NetNamedPipeBinding
有点不同。接下来你尝试使用 keepalive,但是你发现如果你使用调试器逐步执行它,你很容易就会遇到超时。然后你阅读更多论坛,得出的结论是每次都关闭它并不是一个过大的开销 - 当然有很多复制粘贴的代码,但它确实有效。好吧,对我来说就是这样,你的情况可能会有所不同!
此外,如果你收到超时异常,这意味着通道的另一端在一分钟内没有响应 - 这通常意味着你正在尝试以相同的方式进行双向通信,而这被阻塞了。异常如下所示
System.TimeoutException: The read from the pipe did not complete
within the allotted timeout of 00:01:00. The time allotted to this
operation may have been a portion of a longer timeout. --->
System.IO.IOException: The read operation failed, see inner exception. --->
System.TimeoutException: The read from the pipe did not complete within the
allotted timeout of 00:01:00. The time allotted to this operation may have
been a portion of a longer timeout.
at System.ServiceModel.Channels.PipeConnection.WaitForSyncRead
(TimeSpan timeout, Boolean traceExceptionsAsErrors)
at System.ServiceModel.Channels.PipeConnection.Read(Byte[] buffer,
Int32 offset, Int32 size, TimeSpan timeout)
at System.ServiceModel.Channels.DelegatingConnection.Read(Byte[] buffer,
Int32 offset, Int32 size, TimeSpan timeout)
at System.ServiceModel.Channels.ConnectionStream.Read(Byte[] buffer,
Int32 offset, Int32 count, TimeSpan timeout)
at System.ServiceModel.Channels.ConnectionStream.Read(Byte[] buffer,
Int32 offset, Int32 count)
at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
at System.Net.Security.NegotiateStream.StartFrameHeader(Byte[] buffer,
Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.StartReading(Byte[] buffer,
Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer,
Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
--- End of inner exception stack trace ---
at System.Net.Security.NegotiateStream.ProcessRead(Byte[] buffer,
Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.NegotiateStream.Read(Byte[] buffer,
Int32 offset, Int32 count)
at System.ServiceModel.Channels.StreamConnection.Read(Byte[] buffer,
Int32 offset, Int32 size, TimeSpan timeout)
--- End of inner exception stack trace ---
Server stack trace:
at System.ServiceModel.Channels.StreamConnection.Read(Byte[] buffer,
Int32 offset, Int32 size, TimeSpan timeout)
at System.ServiceModel.Channels.SessionConnectionReader.Receive(TimeSpan timeout)
at System.ServiceModel.Channels.SynchronizedMessageSource.Receive(TimeSpan timeout)
at System.ServiceModel.Channels.FramingDuplexSessionChannel.OnClose
(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.OnClose(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Close()
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage
(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke
(MessageData& msgData, Int32 type)
at System.ServiceModel.ICommunicationObject.Close()
历史
- 2010 年 3 月 8 日:文章初稿撰写