C# UDP Socket:获取接收者地址
我有一个异步 UDP 服务器类,其套接字绑定在 IPAddress.Any 上,我想知道接收到的数据包发送到(...或接收到)哪个 IPAddress。看来我不能只使用 Socket.LocalEndPoint 属性,因为它总是返回 0.0.0.0 (这是有道理的,因为它绑定到...)。
以下是我当前使用的代码中有趣的部分:
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.
}
正如前面提到的,这总是打印一行:
Received 32 bytes from 127.0.0.1:1678 to 0.0.0.0:12345
我希望它是这样的:
收到从 127.0.0.1:1678 到 127.0.0.1:12345 的 32 个字节
提前感谢您对此的任何想法! --Adam
更新
好吧,我找到了一个解决方案,尽管我不喜欢它...基本上,我不是打开绑定到 IPAddress.Any 的单个 udp 套接字,而是为每个 IP 地址创建一个唯一的套接字可用的IP地址。所以,新的 Starter 函数看起来像:
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);
}
}
这只是为了说明这个概念,这段代码的最大问题是,每个套接字都试图使用相同的缓冲区...这通常是一个坏主意
...必须有更好的解决方案;我的意思是,源和目标是 UDP 数据包标头的一部分!哦,好吧,我想我会继续这样做,直到有更好的东西为止。
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 that 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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我刚刚遇到了同样的问题。我没有找到使用 ReceiveFrom 或其异步变体来检索接收到的数据包的目标地址的方法。
但是...如果您使用
ReceiveMessageFrom
或其变体,您将获得IPPacketInformation
(参考ReceiveMessageFrom
和EndReceiveMessageFrom< /code>,或作为传递给
ReceiveMessageFromAsync
中的回调的SocketAsyncEventArgs
的属性)。该对象将包含接收数据包的 IP 地址和接口号。(请注意,此代码尚未经过测试,因为我使用的是 ReceiveMessageFromAsync 而不是 fakey-fake Begin/End 调用。)
请注意,您显然应该调用 SetSocketOption(SocketOptionLevel.IP, SocketOptionName. PacketInformation, true) 作为设置套接字的一部分,在您
绑定
之前。 ...ReceiveMessageFrom... 方法将为您设置它,但您可能只能看到设置该选项后 Windows 看到的任何数据包的有效信息。 (实际上,这并不是什么大问题——但是何时/如果发生过,其原因将是一个谜。最好完全防止它发生。)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 anIPPacketInformation
(by reference forReceiveMessageFrom
andEndReceiveMessageFrom
, or as a property of theSocketAsyncEventArgs
passed to your callback inReceiveMessageFromAsync
). 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.)Note, you should apparently call
SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true)
as part of setting up the socket, before youBind
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.)关于缓冲区问题,请尝试以下操作:
创建一个名为 StateObject 的类来存储您想要在回调中使用的任何数据,并使用缓冲区,如果您需要的话还包括套接字(因为我看到您当前正在传递 udpSock作为你的状态对象)。将新创建的对象传递给异步方法,然后您将可以在回调中访问它。
在搜索这个问题时,我发现:
http:// msdn.microsoft.com/en-us/library/system.net.sockets.socket.beginreceivefrom.aspx
然后,您可以从 AsyncState 强制转换 StateObject,就像您当前使用 udpSock 和缓冲区以及其他任何操作一样您需要的数据将存储在那里。
我想现在唯一的问题是如何以及在哪里存储数据,但由于我不知道你的实现,我无法提供帮助。
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.
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.
我认为如果您绑定到 127.0.0.1 而不是 IPAddress.Any,您将获得您想要的行为。
0.0.0.0 故意表示“每个可用的 IP 地址”,并且它非常字面地理解为您的绑定语句的结果。
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.
从该套接字获取地址的一种方法是将其连接到发送者。
一旦完成此操作,您将能够获取本地地址(或至少一个可路由到发送者的地址),但是,您将只能从连接的端点接收消息。
要取消连接,您需要再次使用连接,这次指定一个具有
AF_UNSPEC
系列的地址。不幸的是,我不知道如何在 C# 中实现这一点。(免责声明:我从来没有写过一行C#,这一般适用于Winsock)
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
这还没有测试过……让我们试试
Adam
This is not tested...let's try