【问题标题】:WCF discovery with service hosts using net.tcp://0.0.0.0:0/blah announces net.tcp://0.0.0.0:0/blah使用 net.tcp://0.0.0.0:0/blah 与服务主机的 WCF 发现宣布 net.tcp://0.0.0.0:0/blah
【发布时间】:2013-06-05 18:48:32
【问题描述】:

我想要一个可以侦听所有接口并为每个接口发布发现公告的可发现服务。我希望最终能够使用 tcp://0.0.0.0:0/blah 作为服务端点在配置文件中配置它。但是当我运行下面的代码时,它发出的公告使用 tcp://0.0.0.0:0/blah 作为对客户端无用的 EndpointAddress。

我想接收从 tcp://0.0.0.0:0/blah 派生的每个端点的通知,我更喜欢使用配置文件,而不是像下面这样的程序化服务主机设置。任何解决方法的想法?

    [TestFixtureSetUp]
    public void SetUp()
    {
        service1 = new MyContract();
        EndpointDiscoveryBehavior discoveryBehavior = new EndpointDiscoveryBehavior();
        ServiceDiscoveryBehavior serviceDiscoveryBehavior = new ServiceDiscoveryBehavior(discoveryUri);
        serviceDiscoveryBehavior.AnnouncementEndpoints.Add(new UdpAnnouncementEndpoint(announcementUri));

        serviceHost1 = new ServiceHost(service1,
            new Uri[] {new Uri("net.pipe://localhost"), new Uri("net.tcp://0.0.0.0:0")});
        ServiceEndpoint localEndpoint1 = serviceHost1.AddServiceEndpoint(typeof (IContract),
            new NetNamedPipeBinding(),
            "/Pipe");
        ServiceEndpoint localEndpoint2 = serviceHost1.AddServiceEndpoint(typeof (IContract),
            new NetTcpBinding(),
            "/Tcp");
        localEndpoint2.Behaviors.Add(discoveryBehavior);
        serviceHost1.Description.Behaviors.Add(serviceDiscoveryBehavior);
        serviceHost1.AddServiceEndpoint(new UdpDiscoveryEndpoint(discoveryUri));

        serviceHost1.Open();
    }

【问题讨论】:

  • 你能让你的例子更清楚吗? serviceHost2 在与第一个服务相同的端口上注册相同的 IContract 有什么意义?什么是“discoveryBehaviour”、“serviceDiscoveryBehaviour”等...
  • @Nenad 为清楚起见已编辑。额外的服务主机用于测试与问题无关的内容,因此我将其删除。 discoveryUri 和announcementUri 是你想要使用的任何东西。由于 WCF 网络风暴错误support.microsoft.com/kb/2777305,您无法使它们相同。 :)
  • 嘿,@insipid,考虑看我的答案吗?我在自己的程序中使用它取得了巨大的成功。

标签: c# .net wcf web-services ws-discovery


【解决方案1】:

虽然我的解决方案可能不是“正确的”,但严格来说(这应该在 WCF 本身中修复,如果你问我的话),它有效,并且足以满足我的目的。

首先,声明一个新的端点行为,如下所示:

public class WcfDiscoveryAddressFixEndpointBehavior : IEndpointBehavior, IDispatchMessageInspector
{
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        // Attach ourselves to the MessageInspectors of reply messages
        clientRuntime.CallbackDispatchRuntime.MessageInspectors.Add(this);
    }

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        object messageProperty;
        if (!OperationContext.Current.IncomingMessageProperties.TryGetValue(RemoteEndpointMessageProperty.Name, out messageProperty)) return null;
        var remoteEndpointProperty = messageProperty as RemoteEndpointMessageProperty;
        if (remoteEndpointProperty == null) return null;

        // Extract message body
        string messageBody;
        using (var oldMessageStream = new MemoryStream())
        {
            using (var xw = XmlWriter.Create(oldMessageStream))
            {
                request.WriteMessage(xw);
                xw.Flush();
                messageBody = Encoding.UTF8.GetString(oldMessageStream.ToArray());
            }
        }

        // Replace instances of 0.0.0.0 with actual remote endpoint address
        messageBody = messageBody.Replace("0.0.0.0", remoteEndpointProperty.Address);

        // NOTE: Do not close or dispose of this MemoryStream. It will be used by WCF down the line.
        var newMessageStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody));
        XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(newMessageStream, new XmlDictionaryReaderQuotas());

        // Create a new message with our modified endpoint address and
        // copy over existing properties and headers
        Message newMessage = Message.CreateMessage(xdr, int.MaxValue, request.Version);
        newMessage.Properties.CopyProperties(request.Properties);
        newMessage.Headers.CopyHeadersFrom(request.Headers);
        request = newMessage;
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }
}

此端点行为将原始 WCF 发现回复消息替换为副本,其中0.0.0.0 的实例已替换为接收消息的地址,可在RemoteEndpointMessagePropertyAddress 属性中找到。

要使用它,只需在创建 DiscoveryClient 时将新端点行为添加到 UdpDiscoveryEndpoint

var udpDiscoveryEndpoint = new UdpDiscoveryEndpoint();
udpDiscoveryEndpoint.EndpointBehaviors.Add(new WcfDiscoveryAddressFixEndpointBehavior());
_discoveryClient = new DiscoveryClient(udpDiscoveryEndpoint);

// Proceed as usual.

【讨论】:

  • 太糟糕了,我无法实现这一点,因为我使用的是 ServiceMetadataBehavior。但我喜欢你的思维方式,但我觉得检查和操纵所有反应太麻烦了。你能把它变成一个解决方案,在每个请求中检查端点主机名是否等于客户端使用的主机名,如果不只是更改端点主机名?
  • “公告”呢?我们如何向公告服务添加类似的固定行为?
  • 我找到了解决方案。向 AnnouncementService 添加新的端点行为。在新的端点行为中,在 Alex 的 WcfDiscoveryAddressFixEndpointBehavior 中实现 ApplyDispatchBehavior 而不是 ApplyClientBehavior。
【解决方案2】:

我找到了另一种无需更改消息即可工作的解决方案。

我注意到一个端点有一个address 和一个listening address。想法是为每个ip地址创建一个endpoint,并且只共享一个监听地址(0.0.0.0或者使用机器名来获取ipv6)。

在发现端,所有地址都将被接收,有人可以尝试连接以查找其中一个是可访问的。

服务器部分如下所示:

var listenUri = new Uri("net.tcp://<Environment.MachineName>:<port>/IServer";
var binding = new NetTcpBinding(SecurityMode.None);
foreach (ip address)
{ 
   var addr = new Uri("net.tcp://<ip>:<port>/IServer");
   Host.AddServiceEndpoint(typeof(IService), binding, addr, listenUri)
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 2011-10-01
    • 1970-01-01
    • 2011-02-06
    • 2015-04-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多