简体   繁体   中英

WCF discovery with service hosts using net.tcp://0.0.0.0:0/blah announces net.tcp://0.0.0.0:0/blah

I wanted a discoverable service that would listen on all interfaces and publish discovery announcements for each interface. I was hoping to be able to eventually just configure this in the config file using tcp://0.0.0.0:0/blah as the service endpoint. But when I run the code below, the announcements that it sends out use tcp://0.0.0.0:0/blah as the EndpointAddress which is useless to clients.

I want to receive announcements for every endpoint it derived from tcp://0.0.0.0:0/blah and I would prefer to use a config file and not a programmatic service host setup like below. Any ideas for a workaround?

    [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();
    }

While my solution may not be "correct", strictly speaking (this should really be fixed in WCF itself, if you ask me), it works, and is sufficient for my purposes.

First, declare a new endpoint behavior, like so:

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)
    {
    }
}

This endpoint behavior replaces the original WCF Discovery reply message with a copy in which instances of 0.0.0.0 have been replaced with the address from which the message was received, available in RemoteEndpointMessageProperty 's Address property.

To use it, just add the new endpoint behavior to the UdpDiscoveryEndpoint when you're creating the DiscoveryClient :

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

// Proceed as usual.

I found another solution that works without altering messages.

I noticed that an endpoint has an address and a listening address . The idea is to create an endpoint for each ip address and share only one listening address (0.0.0.0 or use the machine name to get also ipv6).

On the discovery side, all the address will be received, someone could try to connect to find which one of them is reachable.

Ther server part looks like this:

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)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM