问题
I've created an universal app that connects to a WCF webservice at intranet, an it's working just fine, since the address of the service's host is known.
The system's architecture allows to be more than one webservice running, in different hosts, for performance and security (redundancy) reasons. So I'm trying to make my app discover every service, with the given contract, that is been running on the same LAN, but I can't manage to do that.
I'm trying the same approach used at a very similar win32 app:
var discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
var findCriteria = new FindCriteria(typeof(INewProdColetorWCFService));
findCriteria.Duration = TimeSpan.FromSeconds(5);
var findResponse = await discoveryClient.FindTaskAsync(findCriteria);
Visual Studio "automatically" adds the needed reference (System.ServiceModel.Discovery) for me as seen here
At design time it seems to be ok, but when i try to compile, that error appear:
Cannot find type System.ServiceModel.Configuration.ServiceModelConfigurationElementCollection`1 in module System.ServiceModel.dll.
Have any of you did that in UWP? Can you help me? Thanks in advance, iuri.
ps: I've posted this question in MSDN too
回答1:
I came across this thread whilst doing some research myself. After reading up on https://en.wikipedia.org/wiki/WS-Discovery and using Wireshark to decipher some of the specifics, I've got a basic proof of concept that supports Microsoft's WS-Discovery specification, meaning no changes are necessary on the server end.
I've jumped off the project for now, but hopefully someone might get some use from it:
public class WSDiscoveryResponse
{
private readonly string
_content,
_remotePort;
private readonly HostName
_remoteAddress;
public WSDiscoveryResponse(string content, HostName remoteAddress, string remotePort)
{
this._content = content;
this._remoteAddress = remoteAddress;
this._remotePort = remotePort;
}
}
public class WSDiscoveryClient
{
private const string
SRC_PORT = "0",//represents 'use any port available'
DEST_IP_WSDISCOVERY = "239.255.255.250", //broadcast (ish)
DEST_PORT_WSDISCOVERY = "3702";
private TimeSpan _timeout = TimeSpan.FromSeconds(5);
private List<WSDiscoveryResponse> _wsresponses = null;
/// <summary>
/// Get available Webservices
/// </summary>
public async Task<List<WSDiscoveryResponse>> GetAvailableWSEndpoints()
{
_wsresponses = new List<WSDiscoveryResponse>();
using (var socket = new DatagramSocket())
{
try
{
socket.MessageReceived += SocketOnMessageReceived;
//listen for responses to future message
await socket.BindServiceNameAsync(SRC_PORT);
//broadcast interrogation
await SendDiscoveryMessage(socket);
//wait for broadcast responses
await Task.Delay(_timeout).ConfigureAwait(false);
}
catch (Exception ex)
{
SocketErrorStatus webErrorStatus = SocketError.GetStatus(ex.GetBaseException().HResult);
}
}
return _wsresponses;
}
private string BuildDiscoveryMessage()
{
const string outgoingMessageFormat = @"<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope"" xmlns:a=""http://www.w3.org/2005/08/addressing""><s:Header><a:Action s:mustUnderstand=""1"">http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Probe</a:Action><a:MessageID>urn:uuid:{0}</a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand=""1"">urn:docs-oasis-open-org:ws-dd:ns:discovery:2009:01</a:To></s:Header><s:Body><Probe xmlns=""http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01""><d:Types xmlns:d=""http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01"" xmlns:dp0=""http://tempuri.org/"">dp0:IDiscoveryService</d:Types><Duration xmlns=""http://schemas.microsoft.com/ws/2008/06/discovery"">PT5S</Duration></Probe></s:Body></s:Envelope>";
string outgoingMessage = string.Format(outgoingMessageFormat, Guid.NewGuid().ToString());
return outgoingMessage;
}
private async Task SendDiscoveryMessage(DatagramSocket socket)
{
using (var stream = await socket.GetOutputStreamAsync(new HostName(DEST_IP_WSDISCOVERY), DEST_PORT_WSDISCOVERY))
{
string message = BuildDiscoveryMessage();
var data = Encoding.UTF8.GetBytes(message);
using (var writer = new DataWriter(stream))
{
writer.WriteBytes(data);
await writer.StoreAsync();
}
}
}
private void SocketOnMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
var dr = args.GetDataReader();
string message = dr.ReadString(dr.UnconsumedBufferLength);
_wsresponses.Add(new WSDiscoveryResponse(message, args.RemoteAddress, args.RemotePort));
}
}
回答2:
UWP doesn’t support the WS-Discovery API for now. Details please see https://msdn.microsoft.com/en-us/library/windows/apps/mt185502.aspx. There is no System.ServiceModel.Discovery API support for UWP apps in the document. But you can use it in a win32 application. If you need this feature you can submit your idea to UserVoice site: https://wpdev.uservoice.com/forums/110705-universal-windows-platform
回答3:
I don't know if I should answer my own question, but I think it may be useful for anyone trying to do the same, so here it goes.
Since WS-Discovery API is not available in UWP, I had to do it in another way. Using socket was the best alternative I could find. So every WS will listen to a specific port, awaiting for some broadcasted message searching for WS running in the LAN.
The WS implementation is win32, and this is the code needed:
private byte[] dataStream = new byte[1024];
private Socket serverSocket;
private void InitializeSocketServer(string id)
{
// Sets the server ID
this._id = id;
// Initialise the socket
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
// Initialise the IPEndPoint for the server and listen on port 30000
IPEndPoint server = new IPEndPoint(IPAddress.Any, 30000);
// Associate the socket with this IP address and port
serverSocket.Bind(server);
// Initialise the IPEndPoint for the clients
IPEndPoint clients = new IPEndPoint(IPAddress.Any, 0);
// Initialise the EndPoint for the clients
EndPoint epSender = (EndPoint)clients;
// Start listening for incoming data
serverSocket.BeginReceiveFrom(this.dataStream, 0, this.dataStream.Length, SocketFlags.None, ref epSender, new AsyncCallback(ReceiveData), epSender);
}
private void ReceiveData(IAsyncResult asyncResult)
{
// Initialise the IPEndPoint for the clients
IPEndPoint clients = new IPEndPoint(IPAddress.Any, 0);
// Initialise the EndPoint for the clients
EndPoint epSender = (EndPoint)clients;
// Receive all data. Sets epSender to the address of the caller
serverSocket.EndReceiveFrom(asyncResult, ref epSender);
// Get the message received
string message = Encoding.UTF8.GetString(dataStream);
// Check if it is a search ws message
if (message.StartsWith("SEARCHWS", StringComparison.CurrentCultureIgnoreCase))
{
// Create a response messagem indicating the server ID and it's URL
byte[] data = Encoding.UTF8.GetBytes($"WSRESPONSE;{this._id};http://{GetIPAddress()}:5055/wsserver");
// Send the response message to the client who was searching
serverSocket.BeginSendTo(data, 0, data.Length, SocketFlags.None, epSender, new AsyncCallback(this.SendData), epSender);
}
// Listen for more connections again...
serverSocket.BeginReceiveFrom(this.dataStream, 0, this.dataStream.Length, SocketFlags.None, ref epSender, new AsyncCallback(this.ReceiveData), epSender);
}
private void SendData(IAsyncResult asyncResult)
{
serverSocket.EndSend(asyncResult);
}
The client implementation is UWP. I've created the following class to do the search:
public class WSDiscoveryClient
{
public class WSEndpoint
{
public string ID;
public string URL;
}
private List<WSEndpoint> _endPoints;
private int port = 30000;
private int timeOut = 5; // seconds
/// <summary>
/// Get available Webservices
/// </summary>
public async Task<List<WSEndpoint>> GetAvailableWSEndpoints()
{
_endPoints = new List<WSEndpoint>();
using (var socket = new DatagramSocket())
{
// Set the callback for servers' responses
socket.MessageReceived += SocketOnMessageReceived;
// Start listening for servers' responses
await socket.BindServiceNameAsync(port.ToString());
// Send a search message
await SendMessage(socket);
// Waits the timeout in order to receive all the servers' responses
await Task.Delay(TimeSpan.FromSeconds(timeOut));
}
return _endPoints;
}
/// <summary>
/// Sends a broadcast message searching for available Webservices
/// </summary>
private async Task SendMessage(DatagramSocket socket)
{
using (var stream = await socket.GetOutputStreamAsync(new HostName("255.255.255.255"), port.ToString()))
{
using (var writer = new DataWriter(stream))
{
var data = Encoding.UTF8.GetBytes("SEARCHWS");
writer.WriteBytes(data);
await writer.StoreAsync();
}
}
}
private async void SocketOnMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
// Creates a reader for the incoming message
var resultStream = args.GetDataStream().AsStreamForRead(1024);
using (var reader = new StreamReader(resultStream))
{
// Get the message received
string message = await reader.ReadToEndAsync();
// Cheks if the message is a response from a server
if (message.StartsWith("WSRESPONSE", StringComparison.CurrentCultureIgnoreCase))
{
// Spected format: WSRESPONSE;<ID>;<HTTP ADDRESS>
var splitedMessage = message.Split(';');
if (splitedMessage.Length == 3)
{
var id = splitedMessage[1];
var url = splitedMessage[2];
_endPoints.Add(new WSEndpoint() { ID = id, URL = url });
}
}
}
}
}
Feel free to comment if you see something wrong, and please tell me if it helps you someway.
来源:https://stackoverflow.com/questions/37285149/wcf-discovery-in-uwp-app