C#UDP套接字:获取接收者地址

我有一个异步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个字节

在此先感谢您对此的任何想法! – 亚当

UPDATE

好吧,我找到了一个解决方案,虽然我不喜欢它…基本上,我没有打开绑定到IPAddress.Any的单个udp套接字,而是为每个可用的IPAddress创建一个唯一的套接字。 所以,新的Starterfunction如下所示:

 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数据包头的一部分! 哦,好吧,我想我会继续这样做,直到有更好的东西。

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

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

(注意,此代码尚未经过测试,因为我使用的是ReceiveMessageFromAsync而不是伪造的假开始/结束调用。)

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

注意, Bind 之前 ,您应该在设置套接字时调用SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true) 。 … ReceiveMessageFrom …方法将为您设置它,但您可能只会看到在设置选项后Windows看到的任何数据包的有效信息。 (在实践中,这不是一个问题 – 但是如果/如果它曾经发生过,原因将是一个谜。最好完全防止它。)

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

0.0.0.0故意意味着“每个可用的IP地址”,并且由于您的绑定语句而非常直接地理解它。

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

要取消连接,您需要再次使用connect,这次指定一个AF_UNSPEC系列的AF_UNSPEC 。 不幸的是,我不知道如何在C#中实现这一目标。

(免责声明:我从来没有写过一行C#,这一般适用于Winsock)

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

创建一个名为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,以及您需要存储的任何其他数据。

我想现在唯一的问题是如何以及在何处存储数据,但由于我不知道您的实现,我无法帮助。

亚当

这没有经过测试……让我们试试吧

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