I'm working on a UWP/Xamarin.Android app that uses SSDP multicast search to detect other devices on the local network that are running the same app. I've been able to send out multicast messages with UdpClient, but I'm not able to receive multicast messages. It hangs on UdpClient.ReveiveAsync() and never receives a message. Any help in getting this to work would be greatly appreciated.
Here's the coding I'm using to initialize the UdpClient and receive messages:
protected override Task CreateResourcesAsync()
{
address = IPAddress.Parse(Settings.Address ?? "239.255.255.250");
port = Settings.Port ?? 1900;
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
client = new UdpClient() { MulticastLoopback = true };
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
// This line was to test setting SO_REUSE_MULTICASTPORT on Windows, it didn't help
//client.Client.SetSocketOption(SocketOptionLevel.Socket, (SocketOptionName)0x3008, true);
client.Client.Bind(localEndPoint);
client.JoinMulticastGroup(address);
Task.Run(ReceiveThread);
return Task.CompletedTask;
}
async Task ReceiveThread()
{
while (IsActive && client != null)
{
// Does not receive any messages so the Task returned from client.ReceiveAsync() does not complete
UdpReceiveResult request = await client.ReceiveAsync();
}
}
I've tried using different multicast addresses and ports other than the SSDP ones, I've also tried binding to both IPAddress.Any and my local IP address, but neither works.
I'm able to receive multicast messages using the WinRT DatagramSocket, but I can't continue to use WinRT as the app needs to run on Android. I'll include my DatagramSocket code just in case it can be used to help somehow:
DatagramSocket socket = new DatagramSocket();
// If MulticastOnly is not true, it will bind to the port but won't receive messages
socket.Control.MulticastOnly = true;
socket.MessageReceived += async (sender, args) =>
{
// Immediately receives SSDP search requests from various devices on my network
};
await socket.BindServiceNameAsync("1900");
socket.JoinMulticastGroup("239.255.255.250");
Related
I am new to multicast programming. So far I can successfully send and receive multicast messages from two separate processes (a sender and a receiver). My problem is with the receiver...
ReceiverCode:
private static void ReceiveMulticastMessages()
{
var groupEndPoint = new IPEndPoint(IPAddress.Parse("238.8.8.8"), 23888);
var localEndPoint = new IPEndPoint(IPAddress.Any, 23888);
using (var udpClient = new UdpClient())
{
udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpClient.Client.Bind(localEndPoint);
udpClient.JoinMulticastGroup(groupEndPoint.Address, localEndPoint.Address);
while (true)
{
var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
var bytes = udpClient.Receive(ref remoteEndPoint);
var message = Encoding.ASCII.GetString(bytes);
Console.WriteLine(message);
}
}
}
The above code works as long as I specify port 23888 for the localEndPoint. If I change the local port number, no messages are received. I would prefer to set it to 0 so the OS could choose the port. Why can I not specify a different local port than that of multicast group?
Assuming the local endpoint port must match the multicast group port, how does a client deal with a local port conflict?
On the flip side, how can an application (a multicast sender) choose a multicast group port such that any subscribers will not have a port conflict?
When sending any UDP message (not just muticast messages), the port that the sender sends to must match the port that the receiver is listening on. That's how messages get to the right place. If a message is sent to a port other that the one the receiver is bound to, the receiver won't get it.
So a port number needs to be defined that the receiver(s) will listen on and the server will send to.
I am writing a couple of functions to allow me to send out a UDP broadcast / multicast using both IPv4 and IPv6. The follow code does this. The problem I have is that it only does this for a single adapter. If I have two network adapters fitted to my PC it doesn’t send the broadcast out on both. Is it possible to have a single socket configured to handle both IPv4 and IPv6 and to send and receive on all NICs? Or do I have to create separate sockets for each IP address?
public void CreateBroadcaster(CancellationToken cancellationToken, int discoveryPort, int advancePort)
{
_cancellationToken = cancellationToken;
_broadcastEndpoint = new IPEndPoint(IPAddress.Broadcast, advancePort);
var epLocal = new IPEndPoint(IPAddress.IPv6Any, discoveryPort);
_broadcaster = new UdpClient(epLocal);
var soc = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
soc.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
_broadcaster.Client = soc;
_broadcaster.Client.DualMode = true;
_broadcaster.Client.Bind(epLocal);
_broadcaster.EnableBroadcast = true;
_broadcaster.MulticastLoopback = true;
_broadcaster.Client.SetSocketOption(
SocketOptionLevel.IPv6,
SocketOptionName.AddMembership,
new IPv6MulticastOption(IPAddress.Parse("ff02::1")));
}
public void SendPingsOnAdapterLocalSubnets()
{
_broadcaster.Send(_sendData, _sendData.Length, _broadcastEndpoint);
}
You need 2 separate sockets. On the low level, the sockaddr struct will have one or other. IPv4(AF_INET) or IPv6(AF_INET6).
http://www.gnu.org/software/libc/manual/html_node/Address-Formats.html#Address-Formats
Im trying to build a webservice for clients on a local network. For the service i can target any version of the .NET Framework. The clients are mobile windows devices and i would like to use the universal windows platform (UWP) as the target framework.
The service will run on multiple machines with different network addresses. My goal is that a client can automatically detect the service as soon as he connects to that local network. I want to avoid any typing of ip-addresses by the user. But all samples i can find use a hard-coded service URL. Since i have no DNS-Server i have to enter (or hard-code) the service-ip-address into the clients.
Currently im running a WCF service with a UDPDiscoveryEndpoint which does exactly what i want. But unfortunately that part of WCF (the System.ServiceModel.Discovery namespace) is not available on WinRT and also not supported on the universal windows platform. I dont have to use WCF; any alternative library with service discovery functionality would be perfect.
So here is my question: Is there any way to do service discovery on a local network in a WinRT/ UWP App? I tried ASP.NET Web API and SignalR but it seems that this HTTP based services/frameworks dont support discovery at all.
Thank you!
I've managed to do webservice discovery in UWP using socket and broadcast messages.
Plese, check my answer for more details.
EDIT - As sugested by #naveen-vijay, I'm posting a more complete aswer instead of just a link to a solution.
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 });
}
}
}
}
}
In UWP, you can use the PeerFinder class, to discover other instances of your applications in a LAN.
I know this is not exactly service discovery, it's just peer discovery, but it should be enough for your scenario. Just use one instance of the application as your "service" that communicates with the other instances.
You can use this to find your peers and create a socket connection:
PeerFinder.DisplayName = "Doru " + Guid.NewGuid().ToString();
PeerFinder.ConnectionRequested += PeerFinder_ConnectionRequested;
PeerFinder.Start();
private async void PeerFinder_ConnectionRequested(object sender, ConnectionRequestedEventArgs args)
{
PeerInformation peer = args.PeerInformation;
StreamSocket socket = await PeerFinder.ConnectAsync(peer);
}
For a deeper understanding on how Peer Discovery works, checkout this link [minute 6:30].
I am getting HostNotFound error while trying to connect with socket in async. I am sure, that host is working. The strangest part is that all emulator starting from "8.1 u1 *" dont give such error. They connect without any problems. Only my device (htc windows phone 8s) and emulators without 8.1 getting this error. Host address 109.235.68.205 and port 6005. I am targeting 8.0 windows phone. I have no ideas how to solve it.
public string Connect(string hostName, int portNumber)
{
string result = string.Empty;
// Create DnsEndPoint. The hostName and port are passed in to this method.
DnsEndPoint hostEntry = new DnsEndPoint(hostName, portNumber);
// Create a stream-based, TCP socket using the InterNetwork Address Family.
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// Create a SocketAsyncEventArgs object to be used in the connection request
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.RemoteEndPoint = hostEntry;
// Inline event handler for the Completed event.
// Note: This event handler was implemented inline in order to make this method self-contained.
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e)
{
// Retrieve the result of this request
result = e.SocketError.ToString();
// Signal that the request is complete, unblocking the UI thread
_clientDone.Set();
if(OnConnect != null)
OnConnect(true, new ConnectionEventArgs() { Response = result });
});
// Sets the state of the event to nonsignaled, causing threads to block
_clientDone.Reset();
// Make an asynchronous Connect request over the socket
_socket.ConnectAsync(socketEventArg);
// Block the UI thread for a maximum of TIMEOUT_MILLISECONDS milliseconds.
// If no response comes back within this time then proceed
_clientDone.WaitOne(TIMEOUT_MILLISECONDS);
return result;
}
I was able to successfully run your code on the Windows Phone 8 emulators targeting one of my network machines.
The socket.ConnectAsync call is already an Async request that wouldn't block your UI so I don't think you require the manual steps to manage thread synchronization.
But if you insist on this implementation, you can replace the DnsEndPoint with IPEndpoint in your code.
IPEndPoint hostEntry = new IPEndPoint(IPAddress.Parse(hostName), portNumber);
Hope this helps.
I want to to do network discovery using UDP Broadcast in C#. I don't know how to do this. Can you give me advice on how to do it?
I want to do like this tutorial.
It's very simple to make same thing in C#
Server:
var Server = new UdpClient(8888);
var ResponseData = Encoding.ASCII.GetBytes("SomeResponseData");
while (true)
{
var ClientEp = new IPEndPoint(IPAddress.Any, 0);
var ClientRequestData = Server.Receive(ref ClientEp);
var ClientRequest = Encoding.ASCII.GetString(ClientRequestData);
Console.WriteLine("Recived {0} from {1}, sending response", ClientRequest, ClientEp.Address.ToString());
Server.Send(ResponseData, ResponseData.Length, ClientEp);
}
Client:
var Client = new UdpClient();
var RequestData = Encoding.ASCII.GetBytes("SomeRequestData");
var ServerEp = new IPEndPoint(IPAddress.Any, 0);
Client.EnableBroadcast = true;
Client.Send(RequestData, RequestData.Length, new IPEndPoint(IPAddress.Broadcast, 8888));
var ServerResponseData = Client.Receive(ref ServerEp);
var ServerResponse = Encoding.ASCII.GetString(ServerResponseData);
Console.WriteLine("Recived {0} from {1}", ServerResponse, ServerEp.Address.ToString());
Client.Close();
Here is a different solution that is serverless. I had a need to have a bunch of raspberry pis be aware of each other on a network, but had no guarantees of who would be active. So this approach allows everyone to be a client! The complete library is available on GitHub (disclaimer: I created) and that makes this whole process really reaaaally easy for UWP apps.
https://github.com/mattwood2855/WindowsIotDiscovery
This solution assumes that device names are unique and that you want to use JSON strings as the communication protocol, but you could easily just send any other format. Also, in practice try-catch everything ;)
The general mechanism:
Discover your IpAdress
public string IpAddress
{
get
{
var hosts = NetworkInformation.GetHostNames();
foreach (var host in hosts)
{
if (host.Type == HostNameType.Ipv4) return host.DisplayName;
}
return "";
}
}
Set up your listener
var udpPort = "1234";
var socket = new DatagramSocket();
socket.MessageReceived += ReceivedDiscoveryMessage;
await socket.BindServiceNameAsync(udpPort);`
Handle incoming data
async void ReceivedDiscoveryMessage(DatagramSocket socket, DatagramSocketMessageReceivedEventArgs args)
{
// Get the data from the packet
var result = args.GetDataStream();
var resultStream = result.AsStreamForRead();
using (var reader = new StreamReader(resultStream))
{
// Load the raw data into a response object
var potentialRequestString = await reader.ReadToEndAsync();
// Ignore messages from yourself
if (args.RemoteAddress.DisplayName == IpAddress) return;
// Get the message
JObject jRequest = JObject.Parse(potentialRequestString);
// Do stuff with the data
}
}
Send a message
public async void SendDataMessage(string discoveryMessage)
{
// Get an output stream to all IPs on the given port
using (var stream = await socket.GetOutputStreamAsync(new HostName("255.255.255.255"), udpPort))
{
// Get a data writing stream
using (var writer = new DataWriter(stream))
{
// Write the string to the stream
writer.WriteString(discoveryMessage);
// Commit
await writer.StoreAsync();
}
}
}
The idea would be to send a discovery message containing your ip address and name. Then in the receive message function add the ip-name pairs to a List of devices. Add a little logic to avoid duplicates and update Ip address if the ip changes for a given name.
As a bonus, you can have each device send the list of devices they know about. This allows you to minimize udp traffic by not responding when the sender is aware of you. You can even have the receiver compare the list against their own list to discover other devices.
Redundancy is your friend with UDP, there is no guarantee that a packet will be delivered.
I know it's old but someone may still need this...The accepted answer is great but with this little tweak on the server side it's even better.
Fix for the Ilya Suzdalnitski comment (locks up on the second Client.Receive call):
var responseData = Encoding.ASCII.GetBytes("someData");
while (true)
{
var server = new UdpClient(8888);
var clientEp = new IPEndPoint(IPAddress.Any, 0);
var clientRequestData = server.Receive(ref clientEp);
var clientRequest = Encoding.ASCII.GetString(clientRequestData);
Console.WriteLine($"Recived {clientRequest} from {clientEp.Address}, sending
response: {responseData}");
server.Send(responseData, responseData.Length, clientEp);
server.Close();
}
Because after each response the server is closed and recreated, it can work endlessly without locking.
I had the same question but it was not that easy for me as the answer that #rufanov suggests.
Here some situation I had:
Since my application is running normally in a computer that has several network interfaces, I had the problem that the broadcast message was sent only in one of the adapters. To solve this situation I had to get first all the network adapter list and go one by one sending the broadcast message and receiving the answer message.
It is important that you bind the correct localIpEndPoint to your adapters ip address, otherwise you will have problems with the broadcast address by sending.
After some reserch and work I got to this solution. This code corresponds to the server side and will make the network discovery of all devices answering to the braodcast message.
public static void SNCT_SendBroadcast(out List<MyDevice> DevicesList)
{
DevicesList = new List<MyDevice>();
byte[] data = new byte[2]; //broadcast data
data[0] = 0x0A;
data[1] = 0x60;
IPEndPoint ip = new IPEndPoint(IPAddress.Broadcast, 45000); //braodcast IP address, and corresponding port
NetworkInterface[] nics = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); //get all network interfaces of the computer
foreach (NetworkInterface adapter in nics)
{
// Only select interfaces that are Ethernet type and support IPv4 (important to minimize waiting time)
if (adapter.NetworkInterfaceType != NetworkInterfaceType.Ethernet) { continue; }
if (adapter.Supports(NetworkInterfaceComponent.IPv4) == false) { continue; }
try
{
IPInterfaceProperties adapterProperties = adapter.GetIPProperties();
foreach (var ua in adapterProperties.UnicastAddresses)
{
if (ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
//SEND BROADCAST IN THE ADAPTER
//1) Set the socket as UDP Client
Socket bcSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); //broadcast socket
//2) Set socker options
bcSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
bcSocket.ReceiveTimeout = 200; //receive timout 200ms
//3) Bind to the current selected adapter
IPEndPoint myLocalEndPoint = new IPEndPoint(ua.Address, 45000);
bcSocket.Bind(myLocalEndPoint);
//4) Send the broadcast data
bcSocket.SendTo(data, ip);
//RECEIVE BROADCAST IN THE ADAPTER
int BUFFER_SIZE_ANSWER = 1024;
byte[] bufferAnswer = new byte[BUFFER_SIZE_ANSWER];
do
{
try
{
bcSocket.Receive(bufferAnswer);
DevicesList.Add(GetMyDevice(bufferAnswer)); //Corresponding functions to get the devices information. Depends on the application.
}
catch { break; }
} while (bcSocket.ReceiveTimeout != 0); //fixed receive timeout for each adapter that supports our broadcast
bcSocket.Close();
}
}
}
catch { }
}
return;
}
For working example, see that project:https://github.com/xmegz/MndpTray
The server periodically sends broadcast messages. The client side receive and process them. Many host information (Os version, IP address, Network interface, etc..) send trought.
udp broadcast cdp lldp