65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2010年3月8日

CPOL

4分钟阅读

viewsIcon

73485

downloadIcon

2478

我需要在一个服务器进程和多个客户端进程之间进行通信,这是一个简单的测试工具,用于证明这一点

引言

我使用 WCF 和 NetNamedPipeBinding 在我的服务器进程和多个客户端之间进行 IPC,这些客户端将签入并向服务器注册。

客户端需要能够调用服务器上的方法 - 无论是否有参数和返回值,而服务器也需要能够以类似的方式调用客户端上的方法。

在实际的应用示例中,我没有让客户端在退出前取消注册的方法,因此该操作是在服务器端完成的。

这是一张它运行时的图片。

两个客户端都已经注册 - 你可以在服务器上的列表框中看到它们的 GUID

其中一个客户端发送了一条消息,另一个客户端发送了一条匿名消息,然后第一个客户端请求了匿名消息。 

服务器向所有已注册的客户端发送了一条广播消息。

serverclient.jpg

背景

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 日:文章初稿撰写
© . All rights reserved.