C# UDP Socket: Get receiver address
I have an asynchronous UDP server class with a socket bound on IPAddress.Any, and I'd like to know which IPAddress the received packet was sent to (...or received on). It seems that I can't just use the Socket.LocalEndPoint property, as it always returns 0.0.0.0 (which makes sense as it's bound to that...).
Here are the interesting parts of the code I'm currently using:
private Socket udpSock;
private byte[] buffer;
public void Starter(){
//Setup the socket and message buffer
udpSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
udpSock.Bind(new IPEndPoint(IPAddress.Any, 12345));
buffer = new byte[1024];
//Start listening for a new message.
EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
udpSock.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, udpSock);
}
private void DoReceiveFrom(IAsyncResult iar){
//Get the received message.
Socket recvSock = (Socket)iar.AsyncState;
EndPoint clientEP = new IPEndPoint(IPAddress.Any, 0);
int msgLen = recvSock.EndReceiveFrom(iar, ref clientEP);
byte[] localMsg = new byte[msgLen];
Array.Copy(buffer, localMsg, msgLen);
//Start listening for a new message.
EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
udpSock.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, udpSock);
//Handle the received message
Console.WriteLine("Recieved {0} bytes from {1}:{2} to {3}:{4}",
msgLen,
((IPEndPoint)clientEP).Address,
((IPEndPoint)clientEP).Port,
((IPEndPoint)recvSock.LocalEndPoint).Address,
((IPEndPoint)recvSock.LocalEndPoint).Port);
//Do other, more interesting, things with the received message.
}
As mentioned earlier this always prints a line like:
Received 32 bytes from 127.0.0.1:1678 to 0.0.0.0:12345
And I'd like it to be something like:
Received 32 bytes from 127.0.0.1:1678 to 127.0.0.1:12345
Thanks, in advance, for any ideas on this! --Adam
UPDATE
Well, I found a solution, though I don't like it... Basically, instead of opening a single udp socket bound to IPAddress.Any, I create a unique socket for every available IPAddress. So, the new Starter function looks like:
public void Starter(){
buffer = new byte[1024];
//create a new socket and start listening on the loopback address.
Socket lSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
lSock.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345);
EndPoint ncEP = new IPEndPoint(IPAddress.Any, 0);
lSock.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref ncEP, DoReceiveFrom, lSock);
//create a new socket and start listening on each IPAddress in the Dns host.
foreach(IPAddress addr in Dns.GetHostEntry(Dns.GetHostName()).AddressList){
if(addr.AddressFamily != AddressFamily.InterNetwork) continue; //Skip all but IPv4 addresses.
Socket s = new Socket(addr.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
s.Bind(new IPEndPoint(addr, 12345));
EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
s.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, s);
}
}
This is just to illustrate the concept, the biggest problem with this code as is, is tha开发者_高级运维t each socket is trying to use the same buffer... which is generally a bad idea...
There has to be a better solution to this; I mean, the source and destination are part of the UDP packet header! Oh well, I guess I'll go with this until there's something better.
I just had the same problem. I don't see a way, using ReceiveFrom
or its async variants, to retrieve the destination address of a received packet.
However...If you use ReceiveMessageFrom
or its variants, you'll get an IPPacketInformation
(by reference for ReceiveMessageFrom
and EndReceiveMessageFrom
, or as a property of the SocketAsyncEventArgs
passed to your callback in ReceiveMessageFromAsync
). That object will contain the IP address and interface number where the packet was received.
(Note, this code has not been tested, as i used ReceiveMessageFromAsync
rather than the fakey-fake Begin/End calls.)
private void ReceiveCallback(IAsyncResult iar)
{
IPPacketInformation packetInfo;
EndPoint remoteEnd = new IPEndPoint(IPAddress.Any, 0);
SocketFlags flags = SocketFlags.None;
Socket sock = (Socket) iar.AsyncState;
int received = sock.EndReceiveMessageFrom(iar, ref flags, ref remoteEnd, out packetInfo);
Console.WriteLine(
"{0} bytes received from {1} to {2}",
received,
remoteEnd,
packetInfo.Address
);
}
Note, you should apparently call SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true)
as part of setting up the socket, before you Bind
it. The ...ReceiveMessageFrom... methods will set it for you, but you'll probably only see valid info on any packets Windows saw after the option was set. (In practice, this isn't much of an issue -- but when/if it ever happened, the reason would be a mystery. Better to prevent it altogether.)
In regard to the buffer problem try the following:
Create a class called StateObject to store any data you want to have in your callback, with a buffer, also including the socket if you so need it (as I see that you are currently passing udpSock as your stateObject). Pass the newly created object to the async method and then you will have access to it in your callback.
public void Starter(){
StateObject state = new StateObject();
//set any values in state you need here.
//create a new socket and start listening on the loopback address.
Socket lSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
lSock.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345);
EndPoint ncEP = new IPEndPoint(IPAddress.Any, 0);
lSock.BeginReceiveFrom(state.buffer, 0, state.buffer.Length, SocketFlags.None, ref ncEP, DoReceiveFrom, state);
//create a new socket and start listening on each IPAddress in the Dns host.
foreach(IPAddress addr in Dns.GetHostEntry(Dns.GetHostName()).AddressList){
if(addr.AddressFamily != AddressFamily.InterNetwork) continue; //Skip all but IPv4 addresses.
Socket s = new Socket(addr.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
s.Bind(new IPEndPoint(addr, 12345));
EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
StateObject objState = new StateObject();
s.BeginReceiveFrom(objState.buffer, 0, objState.buffer.length, SocketFlags.None, ref newClientEP, DoReceiveFrom, objState);
}
}
In searching this question I found:
http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.beginreceivefrom.aspx
You can then cast the StateObject from AsyncState as you are currently doing with udpSock and your buffer, as well as anyother data you need would be stored there.
I suppose that now the only problem is how and where to store the data, but as I don't know your implementation I can't help there.
I think that if you bind to 127.0.0.1 instead of IPAddress.Any you'll get the behavior that you want.
0.0.0.0 deliberately means "every IP address available" and it takes it very literally as a consequence of your bind statement.
One way you'll get an address from that socket would be to connect it to the sender. Once you do that, you'll be able to get the local address (or at least, one routable to the sender), however, you'll only be able to receive messages from the connected endpoint.
To unconnect, you'll need to use connect again, this time specifying an address with a family of AF_UNSPEC
. Unfortunately, I don't know how this would be achieved in C#.
(Disclaimer: I've never written a line of C#, this applies to Winsock in general)
Adam
This is not tested...let's try
private void DoReceiveFrom(IAsyncResult iar){
//Get the received message.
Socket recvSock = (Socket)iar.AsyncState;
//EndPoint clientEP = new IPEndPoint(IPAddress.Any, 0);
Socket clientEP = recvSock.EndAccept(iar);
int msgLen = recvSock.EndReceiveFrom(iar, ref clientEP);
byte[] localMsg = new byte[msgLen];
Array.Copy(buffer, localMsg, msgLen);
//Start listening for a new message.
EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
udpSock.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP, DoReceiveFrom, udpSock);
//Handle the received message
/*
Console.WriteLine("Recieved {0} bytes from {1}:{2} to {3}:{4}",
msgLen,
((IPEndPoint)recvSock.RemoteEndPoint).Address,
((IPEndPoint)recvSock.RemoteEndPoint).Port,
((IPEndPoint)recvSock.LocalEndPoint).Address,
((IPEndPoint)recvSock.LocalEndPoint).Port);
//Do other, more interesting, things with the received message.
*/
Console.WriteLine("Recieved {0} bytes from {1}:{2} to {3}",
msgLen,
((IPEndPoint)recvSock.RemoteEndPoint).Address,
((IPEndPoint)recvSock.RemoteEndPoint).Port,
clientEP.RemoteEP.ToString();
}
精彩评论