19

我有一个异步 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.
}

如前所述,这总是打印如下一行:

从 127.0.0.1:1678 到 0.0.0.0:12345 收到 32 个字节

我希望它是这样的:

从 127.0.0.1:1678 到 127.0.0.1:12345 收到 32 个字节

提前感谢您对此的任何想法! - 亚当

更新

好吧,我找到了一个解决方案,虽然我不喜欢它......基本上,我不是打开一个绑定到 IPAddress.Any 的单个 udp 套接字,而是为每个可用的 IPAddress 创建一个唯一的套接字。因此,新的 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 数据包头的一部分!哦,好吧,我想我会继续这样做,直到有更好的东西。

4

5 回答 5

17

我只是有同样的问题。我看不到使用ReceiveFrom或其异步变体的方法来检索接收到的数据包的目标地址。

但是...如果您使用ReceiveMessageFrom或其变体,您将获得一个IPPacketInformation(通过对ReceiveMessageFromand的引用EndReceiveMessageFrom,或作为SocketAsyncEventArgs传递给您的回调 in的属性ReceiveMessageFromAsync)。该对象将包含接收数据包的 IP 地址和接口号。

(注意,此代码尚未经过测试,因为我使用ReceiveMessageFromAsync了而不是假冒的 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)在设置套接字之前调用 Bind 。...ReceiveMessageFrom... 方法将为您设置它,但您可能只会在设置选项后 Windows 看到的任何数据包上看到有效信息。(在实践中,这并不是什么大问题——但何时/如果它发生了,原因将是一个谜。最好完全阻止它。)

于 2011-04-26T19:50:51.300 回答
1

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

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

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

然后,您可以像当前对 udpSock 和缓冲区所做的那样从 AsyncState 转换 StateObject,以及您需要的任何其他数据将存储在那里。

我想现在唯一的问题是如何以及在哪里存储数据,但由于我不知道你的实现,所以我无能为力。

于 2011-05-02T19:19:15.137 回答
0

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

要断开连接,您需要再次使用 connect,这次指定一个带有AF_UNSPEC. 不幸的是,我不知道如何在 C# 中实现这一点。

(免责声明:我从未写过一行 C#,这通常适用于 Winsock)

于 2010-11-10T19:01:43.547 回答
0

我认为如果你绑定到 127.0.0.1 而不是 IPAddress.Any 你会得到你想要的行为。

0.0.0.0 故意表示“每个可用的 IP 地址”,并且由于您的 bind 语句,它非常字面意思。

于 2010-11-10T18:46:31.510 回答
-2

亚当

这未经测试...让我们试试

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();
 }
于 2010-11-10T19:01:31.130 回答