C# UDP Socket:获取接收者地址

发布于 2024-10-01 10:48:22 字数 3259 浏览 6 评论 0原文

我有一个异步 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

幽蝶幻影 2024-10-08 10:48:22

我刚刚遇到了同样的问题。我没有找到使用 ReceiveFrom 或其异步变体来检索接收到的数据包的目标地址的方法。

但是...如果您使用 ReceiveMessageFrom 或其变体,您将获得 IPPacketInformation (参考 ReceiveMessageFromEndReceiveMessageFrom< /code>,或作为传递给 ReceiveMessageFromAsync 中的回调的 SocketAsyncEventArgs 的属性)。该对象将包含接收数据包的 IP 地址和接口号。

(请注意,此代码尚未经过测试,因为我使用的是 ReceiveMessageFromAsync 而不是 fakey-fake Begin/End 调用。)

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
    );
}

请注意,您显然应该调用 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 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.)

骑趴 2024-10-08 10:48:22

关于缓冲区问题,请尝试以下操作:

创建一个名为 StateObject 的类来存储您想要在回调中使用的任何数据,并使用缓冲区,如果您需要的话还包括套接字(因为我看到您当前正在传递 udpSock作为你的状态对象)。将新创建的对象传递给异步方法,然后您将可以在回调中访问它。

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);
     }
} 

在搜索这个问题时,我发现:

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.

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.

吾性傲以野 2024-10-08 10:48:22

我认为如果您绑定到 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.

习ぎ惯性依靠 2024-10-08 10:48:22

从该套接字获取地址的一种方法是将其连接到发送者。
一旦完成此操作,您将能够获取本地地址(或至少一个可路由到发送者的地址),但是,您将只能从连接的端点接收消息。

要取消连接,您需要再次使用连接,这次指定一个具有 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)

薔薇婲 2024-10-08 10:48:22

Adam

这还没有测试过……让我们试试

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();
 }

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();
 }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文