I'm trying to do a webservice discovery using WCF's DiscoveryClient using this code:
// Setup the discovery client (WSDiscovery April 2005)
DiscoveryEndpoint discoveryEndpoint = new UdpDiscoveryEndpoint(DiscoveryVersion.WSDiscoveryApril2005);
DiscoveryClient discoveryClient = new DiscoveryClient(discoveryEndpoint);
// Setup the wanted device criteria
FindCriteria criteria = new FindCriteria();
criteria.ScopeMatchBy = new Uri("http://schemas.xmlsoap.org/ws/2005/04/discovery/rfc3986");
criteria.Scopes.Add(new Uri("onvif://www.onvif.org/"));
// Go find!
criteria.Duration = TimeSpan.FromMilliseconds(duration);
discoveryClient.FindAsync(criteria, this);
This works very well on a machine with a single IP address (10.1.4.25) assigned to the single network interface. The broadcast is sent from 10.1.4.25 to 239.255.255.250, and I get responses from 5 devices all on the same subnet.
However, when the machine has multiple IPs on the same interface, it seems to pick a single source IP and sends the request from that.
In this case, I get a reply from a single device giving a 169.254 address.
I have tried setting UdpDiscoveryEndpoint.TransportSettings.MulticastInterfaceId to a suitable interface ID which hasn't helped as it identifies a single interface, not a specific IP.
The UdpDiscoveryEndpoint.ListenUri property also returns the multicast address, and so won't effect the source IP.
UdpDiscoveryEndpoint.Address is the URN for the discovery protocol.
Is there any way I can force it to send from a specific IP address, or ideally, multiple requests on each configured IP?
I have also tried ONVIF Device Manager that seems to have the same problem.
Note that this is not about making a service bind to a specific, or "all address" IP. It is about the IP a discovery request is sent from.
Well, I had the same problem and after some days of research, reading ONVIF documents and learning some tips about multicasting, I developed this code which works fine.
As an example the main IP address on my network adapter is 192.168.80.55 and I also set another IP(192.168.0.10) in advanced settings. With use of this code I can discover device service of a camera with the IP address of 192.168.0.12.
The most important part of this sample is "DeepDiscovery" method which contains the main idea of iteration on network addresses and multicasting proper Probe message.
I recommend deserialization of the response in "GetSocketResponse" method. Currently, just I extract the service URI using Regex.
As mentioned in this article (https://msdn.microsoft.com/en-us/library/dd456791(v=vs.110).aspx):
For WCF Discovery to work correctly, all NICs (Network Interface
Controller) should only have 1 IP address.
I am doing the exact action that WS-Discovery does and using the standard 3702 port, but I myself build the SOAP envelope
and use Socket class for sending the packet for all IP addresses that have been set for the network interface controller.
class Program
{
static readonly List<string> addressList = new List<string>();
static readonly IPAddress multicastAddress = IPAddress.Parse("239.255.255.250");
const int multicastPort = 3702;
const int unicastPort = 0;
static void Main(string[] args)
{
DeepDiscovery();
Console.ReadKey();
}
public static void DeepDiscovery()
{
string probeMessageTemplate = #"<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope"" xmlns:a=""http://schemas.xmlsoap.org/ws/2004/08/addressing""><s:Header><a:Action s:mustUnderstand=""1"">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</a:Action><a:MessageID>urn:uuid:{messageId}</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand=""1"">urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To></s:Header><s:Body><Probe xmlns=""http://schemas.xmlsoap.org/ws/2005/04/discovery""><d:Types xmlns:d=""http://schemas.xmlsoap.org/ws/2005/04/discovery"" xmlns:dp0=""http://www.onvif.org/ver10/device/wsdl"">dp0:Device</d:Types></Probe></s:Body></s:Envelope>";
foreach (IPAddress localIp in
Dns.GetHostAddresses(Dns.GetHostName()).Where(i => i.AddressFamily == AddressFamily.InterNetwork))
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(new IPEndPoint(localIp, unicastPort));
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, localIp));
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 255);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.MulticastLoopback = true;
var thread = new Thread(() => GetSocketResponse(socket));
var probeMessage = probeMessageTemplate.Replace("{messageId}", Guid.NewGuid().ToString());
var message = Encoding.UTF8.GetBytes(probeMessage);
socket.SendTo(message, 0, message.Length, SocketFlags.None, new IPEndPoint(multicastAddress, multicastPort));
thread.Start();
}
}
public static void GetSocketResponse(Socket socket)
{
try
{
while (true)
{
var response = new byte[3000];
EndPoint ep = socket.LocalEndPoint;
socket.ReceiveFrom(response, ref ep);
var str = Encoding.UTF8.GetString(response);
var matches = Regex.Matches(str, #"http://\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/onvif/device_service");
foreach (var match in matches)
{
var value = match.ToString();
if (!addressList.Contains(value))
{
Console.WriteLine(value);
addressList.Add(value);
}
}
//...
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
//...
}
}
}
Related
I have built a peer to peer C# video conferencing application that uses a specific TCP port(17500) for audio communication. Currently, on my application interface, I enter the other IP address which has the program opened in order to communicate. What I want to do is to find the IP addresses automatically.
So, I though the best way to achieve this is to obtain the local IP addresses that are using the same TCP port number, 17500. How can I do that ? or is there any other methods getting IP addresses using the same application ?
As mentioned in comments, you need some kind of peer-discovery protocol.
As many multimedia devices, routers etc. use multicast based discovery protocols like SSDP, I created a similar discovery service sample .
Usage is simple. Just use
Discoverer.PeerJoined = ip => Console.WriteLine("JOINED:" + ip);
Discoverer.PeerLeft= ip => Console.WriteLine("LEFT:" + ip);
Discoverer.Start();
All your clients will use the same code.
using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Caching; // add this library from the reference tab
using System.Text;
using System.Threading.Tasks;
namespace SO
{
public class Discoverer
{
static string MULTICAST_IP = "238.212.223.50"; //Random between 224.X.X.X - 239.X.X.X
static int MULTICAST_PORT = 2015; //Random
static UdpClient _UdpClient;
static MemoryCache _Peers = new MemoryCache("_PEERS_");
public static Action<string> PeerJoined = null;
public static Action<string> PeerLeft = null;
public static void Start()
{
_UdpClient = new UdpClient();
_UdpClient.Client.Bind(new IPEndPoint(IPAddress.Any, MULTICAST_PORT));
_UdpClient.JoinMulticastGroup(IPAddress.Parse(MULTICAST_IP));
Task.Run(() => Receiver());
Task.Run(() => Sender());
}
static void Sender()
{
var IamHere = Encoding.UTF8.GetBytes("I AM ALIVE");
IPEndPoint mcastEndPoint = new IPEndPoint(IPAddress.Parse(MULTICAST_IP), MULTICAST_PORT);
while (true)
{
_UdpClient.Send(IamHere, IamHere.Length, mcastEndPoint);
Task.Delay(1000).Wait();
}
}
static void Receiver()
{
var from = new IPEndPoint(0, 0);
while (true)
{
_UdpClient.Receive(ref from);
if (_Peers.Add(new CacheItem(from.Address.ToString(), from),
new CacheItemPolicy() {
SlidingExpiration = TimeSpan.FromSeconds(20),
RemovedCallback = (x) => { if (PeerLeft != null) PeerLeft(x.CacheItem.Key); }
}
)
)
{
if (PeerJoined != null) PeerJoined(from.Address.ToString());
}
Console.WriteLine(from.Address.ToString());
}
}
}
}
Now a little bit about the algorithm:
Every client multicasts a packet every seconds.
if the receiver(every client has it) gets a packet from an IP that isn't in its cache, it will fire PeerJoined method.
Cache will expire in 20 seconds. If a client doesn't receive a packet within that duration from another client in cache, it will fire PeerLeft method.
I believe if you are using a peer to peer application to exchange packets, when you need to know if someone "Is Online and Ready for connection", you need to send a broadcast. We can do it easily using an UDP connection.
I'll post an example where you use two methods: one to ask the entire network for ready clients in a broadcast message, and the other will start a listener to answer back broadcast asking message, or start a connection if a response of type "i am here" comes.
Hope it helps!
public sealed class UdpUtility
{
// Our UDP Port
private const int broadcastPort = 11000;
// Our message to ask if anyone is ready for connection
private const string askMessage = "ARE ANYONE OUT THERE?";
// Our answer message
private const string responseMessage = "I AM HERE!";
// We use this method to look for a client to connect with us.
// It will send a broadcast to the network, asking if any client is ready for connection.
public void SendBroadcastMessage()
{
var udp = new UdpClient(broadcastPort);
var endpoint = new IPEndPoint(IPAddress.Broadcast, broadcastPort);
try
{
var bytes = Encoding.ASCII.GetBytes(askMessage);
udp.Send(bytes, bytes.Length, endpoint);
}
catch (Exception ex)
{
// Treat your connection exceptions here!
}
}
// This method will start a listener on the port.
// The client will listen for the ask message and the ready message.
// It can then, answer back with a ready response, or start the TCP connection.
public void ListenBroadcastMessage()
{
var udp = new UdpClient(broadcastPort);
var endpoint = new IPEndPoint(IPAddress.Broadcast, broadcastPort);
bool received = false;
try
{
while (!received)
{
// We start listening broadcast messages on the broadcast IP Address interface.
// When a message comes, the endpoing IP Address will be updated with the sender IP Address.
// Then we can answer back the response telling that we are here, ready for connection.
var bytes = udp.Receive(ref endpoint);
var message = Encoding.ASCII.GetString(bytes);
if (message == askMessage)
{
// Our client received the ask message. We must answer back!
// When the client receives our response, his endpoint will be updated with our IP Address.
// The other client can, then, start the TCP connection and do the desired stuff.
var responseBytes = Encoding.ASCII.GetBytes(responseMessage);
udp.Send(responseBytes, responseBytes.Length, endpoint);
}
else if (message == responseMessage)
{
// We received a connection ready message! We can stop listening.
received = true;
// We received a response message!
// We can start our TCP connection here and do the desired stuff.
// Remember: The other client IP Address (the thing you want) will be on the
// endpoint object at this point. Just use it and start your TCP connection!
}
}
}
catch (Exception ex)
{
// Treat your connection exceptions here!
}
}
}
Invoke your command prompt to do "netstat -n" and extract the output.
Here is a piece of code taken from a program that I have wrote modified to fit your requirements. You will still need to further process the data to get the IP addresses
Process netP = new Process();
ProcessStartInfo netPI = new ProcessStartInfo();
netPI.FileName = "cmd";
netPI.UseShellExecute = false;
netPI.RedirectStandardOutput = true;
netPI.RedirectStandardInput = true;
netPI.RedirectStandardError = true;
netPI.CreateNoWindow = true;
netP.StartInfo = NetPI;
netP.Start();
while (!netP.Start())
Thread.Sleep(100);
StreamWriter sW = netP.StandardInput;
StreamReader sR = netP.StandardOutput;
sW.WriteLine("netstat -n")
sW.Close();
string data = sR.ReadToEnd();
sR.Close();
//Do some further processing to filter out the addresses and extract
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 have my Netduino Plus 2 go out to a web service to look up some values that I would like it to use in my project. One of the values that I have the Netduino check is its preferred IP address. If the Netduino has a different IPAddress than its preferred, I want to change it.
I have a method in my project called BindIPAddress (below) that takes a string.
I am getting a SocketException with a code of 10022 for invalid argument. This happens when I call this.Socket.Bind. My class has a property called Socket to hold the Socket value. Is it because my socket already has an endpoint ? I tried adding this.Socket = null and then this.Socket = new (....... thinking we need a new socket to work with, but this returned the same error.
Please advise how I can change my IP address from one static IP address to another.
public void BindIPAddress(string strIPAddress)
{
try
{
Microsoft.SPOT.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0].EnableStaticIP(strIPAddress, "255.255.240.0", "10.0.0.1");
Microsoft.SPOT.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0].EnableStaticDns(new string[] { "10.0.0.2", "10.0.0.3" });
IPEndPoint ep = new IPEndPoint(IPAddress.Parse(strIPAddress), 80);
this.Socket.Bind(ep);
this.IpAddress = strIPAddress;
}
catch(SocketException exc)
{
Debug.Print(exc.Message);
Debug.Print(exc.ErrorCode.ToString());
}
catch(Exception ex)
{
Debug.Print(ex.Message);
}
//Debug.Print(ep.Address.ToString());
}
There may be 2 possible solutions to this problem. The first one is, you can programatically set the preffered IP addresses as the way you have tried to do so and the second one is, you can use MFDeploy tool, which comes with .NET Micro Framework SDK bundle that allows you to set your embedded device network configuration statically before running your application on it.
1) Since you have not provided rest of the code, here's a proper way to bind your socket to an EndPoint (actually, I would not design that class and binding function as the way you posted here, but just wanted to underline the missing parts of your code):
public void BindIPAddress(string strIPAddr)
{
Socket sock = null;
IPEndPoint ipe = null;
NetworkInterface[] ni = null;
try
{
ipe = new IPEndPoint(IPAddress.Parse(strIPAddr), 80);
sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // Assuming the WebService is connection oriented (TCP)
// sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); // if it broadcasts UDP packets, use this line (UDP)
ni = NetworkInterface.GetAllNetworkInterfaces();
if (ni != null && ni.Length > 0)
{
ni[0].EnableStaticIP(strIPAddr, "255.255.240.0", "10.0.0.1");
ni[0].EnableStaticDns(new string[2] { "10.0.0.2", "10.0.0.3" });
sock.Bind(ipe);
this.Socket = sock;
this.IpAddress = strIPAddr;
}
else
throw new Exception("Network interface could not be retrieved successfully!");
}
catch(Exception ex)
{
Debug.Print(ex.Message);
}
}
2) Or without programming, just by using MFDeploy tool you can set the preferred IP addresses after plugging your embedded device into your PC and following the path below:
MFDeploy > Target > Configuration > Network
then enter the preferred IP addresses. That's simply all.
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
I've the following code to read multicast message coming from the network, for a specified IP+Port
private static void ReceiveMessages(int port, string ip, CancellationToken token)
{
Task.Factory.StartNew(() =>
{
using (var mUdpClientReceiver = new UdpClient())
{
var mReceivingEndPoint = new IPEndPoint(IPAddress.Any, port);
mUdpClientReceiver.ExclusiveAddressUse = false;
mUdpClientReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
mUdpClientReceiver.ExclusiveAddressUse = false;
mUdpClientReceiver.Client.Bind(mReceivingEndPoint);
mUdpClientReceiver.JoinMulticastGroup(IPAddress.Parse(ip), 255);
while (!token.IsCancellationRequested)
{
byte[] receive = mUdpClientReceiver.Receive(ref mReceivingEndPoint);
Console.WriteLine("Message received from {0} ",mReceivingEndPoint);
}
}
});
}
I've two network adapter from which I've data coming on this multicast ip+port(confirmed by two instances of wireshark monitoring each network adapter). I see on wireshark a lot of traffic coming on those port+Ip) for both network cards.
The problem is that on my console, I only see messages coming from one network card.
I double checked with netstat, I don't have any other software listening on my port:
So why am I getting traffic from only one of my two network cards?
EDIT:
I even tried the following:
private static void ReceiveMessages(int port, string ip, CancellationToken token, IEnumerable<IPAddress> ipAddresses)
{
foreach (IPAddress ipAddress in ipAddresses)
{
IPAddress ipToUse = ipAddress;
Task.Factory.StartNew(() =>
{
using (var mUdpClientReceiver = new UdpClient())
{
var mReceivingEndPoint = new IPEndPoint(ipToUse, port);
mUdpClientReceiver.ExclusiveAddressUse = false;
mUdpClientReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
mUdpClientReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
mUdpClientReceiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1);
mUdpClientReceiver.ExclusiveAddressUse = false;
mUdpClientReceiver.Client.Bind(mReceivingEndPoint);
mUdpClientReceiver.JoinMulticastGroup(IPAddress.Parse(ip), 255);
Console.WriteLine("Starting to listen on "+ipToUse);
while (!token.IsCancellationRequested)
{
byte[] receive = mUdpClientReceiver.Receive(ref mReceivingEndPoint);
Console.WriteLine("Message received from {0} on {1}", mReceivingEndPoint,ipToUse);
}
}
});
}
}
I see the "Starting to listen on theCorrectIP" twice(for my two IPs), but it still display only data coming from one network card.
EDIT 2
I did notice something else that is strange too. If I disable the interface on which I receive all data, and then start the software, I now get data from the other interface. If I activate again the interface and restart the software, I still get the traffic on the non-deactivated card.
And I know for sure that I've devices that respond to me, that are connected only to one network(not both)
EDIT 3
Another thing: if I send a message from me(localhost), on all network card that I've, I see them coming on my two network interfaces. BUT, if I start my program twice, only the first programm get messages, not the second one.
Edit 4
Additional info, following the first comment:
I've two ethernet cards, one with the 10.10.24.78 ip, the other with the 10.9.10.234 ip.
It's not me that send data, but network pieces(the port 5353 with this ip is a know multicast address used for mDNS, so I should receive traffic from things like printer, itunes, macs, and some other pieces of software we created). Data are multicasted on the ip
224.0.0.251 and port 5353.
Here is a code that you could use to send data on severals IPs, but like I described, if you start it in local it almost works(except that only one local client receive the message).
private static void SendManuallyOnAllCards(int port, string multicastAddress, IEnumerable<IPAddress> ipAddresses)
{
foreach (IPAddress remoteAddress in ipAddresses)
{
IPAddress ipToUse = remoteAddress;
using (var mSendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
{
mSendSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership,
new MulticastOption(IPAddress.Parse(multicastAddress)));
mSendSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 255);
mSendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
var ipep = new IPEndPoint(ipToUse, port);
//IPEndPoint ipep = new IPEndPoint(IPAddress.Parse(multicastAddress), port);
mSendSocket.Bind(ipep);
mSendSocket.Connect(ipep);
byte[] bytes = Encoding.ASCII.GetBytes("This is my welcome message");
mSendSocket.Send(bytes, bytes.Length, SocketFlags.None);
}
}
}
EDIT 5
Here is the result of my route print(Didn't know that command), and on my two IPs, I always receive data on the 10.9.10.234
Edit 6
I tried several other things:
Use a socket to receive instead of the UdpClient --> Didn't worked
Set some addition socketOption on the reader(DontRoute =1, Broadcast=1) -->Didn't worked
Specify the MulticastInterface that the reader Socket has to use(using socketOption MulticastInterface) --> Didn't work
I had the same problem that I wanted to receive multicasts from all my network interfaces. As EJP already said, you need to call JoinMulticastGroup(IPAddress multicastAddr, IPAddress localAddress) on the UdpClient for all network interfaces:
int port = 1036;
IPAddress multicastAddress = IPAddress.Parse("239.192.1.12");
client = new UdpClient(new IPEndPoint(IPAddress.Any, port));
// list of UdpClients to send multicasts
List<UdpClient> sendClients = new List<UdpClient>();
// join multicast group on all available network interfaces
NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface networkInterface in networkInterfaces)
{
if ((!networkInterface.Supports(NetworkInterfaceComponent.IPv4)) ||
(networkInterface.OperationalStatus != OperationalStatus.Up))
{
continue;
}
IPInterfaceProperties adapterProperties = networkInterface.GetIPProperties();
UnicastIPAddressInformationCollection unicastIPAddresses = adapterProperties.UnicastAddresses;
IPAddress ipAddress = null;
foreach (UnicastIPAddressInformation unicastIPAddress in unicastIPAddresses)
{
if (unicastIPAddress.Address.AddressFamily != AddressFamily.InterNetwork)
{
continue;
}
ipAddress = unicastIPAddress.Address;
break;
}
if (ipAddress == null)
{
continue;
}
client.JoinMulticastGroup(multicastAddress, ipAddress);
UdpClient sendClient = new UdpClient(new IPEndPoint(ipAddress, port));
sendClients.Add(sendClient);
}
I am also creating a list of UdpClients so I can send my multicasts on all network interfaces.
I finally found how to do it!
In fact, if I keep exactly the same code, but using it with async methods, it work!!! I just can't understand why it doesn't work with sync method(if someone knows, you're welcome to tell me :) )
Since I've lost 3 days on this, I think it worth an example:
private static void ReceiveAsync(int port, string address, IEnumerable<IPAddress> localAddresses)
{
IPAddress multicastAddress = IPAddress.Parse(address);
foreach (IPAddress localAddress in localAddresses)
{
var udpClient = new UdpClient(AddressFamily.InterNetwork);
udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpClient.Client.Bind(new IPEndPoint(localAddress, port));
udpClient.JoinMulticastGroup(multicastAddress, localAddress);
udpClient.BeginReceive(OnReceiveSink,
new object[]
{
udpClient, new IPEndPoint(localAddress, ((IPEndPoint) udpClient.Client.LocalEndPoint).Port)
});
}
}
And the async method:
private static void OnReceiveSink(IAsyncResult result)
{
IPEndPoint ep = null;
var args = (object[]) result.AsyncState;
var session = (UdpClient) args[0];
var local = (IPEndPoint) args[1];
byte[] buffer = session.EndReceive(result, ref ep);
//Do what you want here with the data of the buffer
Console.WriteLine("Message received from " + ep + " to " + local);
//We make the next call to the begin receive
session.BeginReceive(OnReceiveSink, args);
}
I hope that helps ;)
You need to join the multicast group via all available interfaces. By default, the outgoing IGMP JOIN message will be routed according to the unicast routing tables, which will send it out via the 'cheapest' route, using whichever NIC accesses that route. If your multicast group can be sourced via more than one of those routes, you need to iterate.