2010-11-10 75 views
17

我有一個異步UDP服務器類,並在IPAddress.Any上綁定了一個套接字,我想知道接收的數據包發送到哪個IPAddress(...或接收)。看來,我不能只使用Socket.LocalEndPoint屬性,因爲它總是返回0.0.0.0(這是有道理的,因爲它是綁定到...)。C#UDP Socket:獲取接收者地址

這裏是我目前使用的代碼有趣的部分:

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收到的32個字節: 12345

而且我想它是這樣的:

收到32個字節127.0.0.1:1678到127.0.0.1:12345

感謝,提前,有這方面的想法! --Adam

UPDATE

嗯,我找到了一個解決方案,但我不喜歡它...基本上,而不是打開勢必IPAddress.Any一個UDP套接字,我創建了一個每個可用的IP地址都有獨特的套接字。因此,新的啓動函數看起來像:

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數據包頭的一部分!哦,我想我會一直這樣做,直到有更好的東西。

回答

14

我有同樣的問題。我沒有看到一種方法,使用ReceiveFrom或它的異步變體來檢索接收到的數據包的目標地址。

但是...如果您使用ReceiveMessageFrom或其變體,您將得到IPPacketInformation(通過參考ReceiveMessageFromEndReceiveMessageFrom,或作爲SocketAsyncEventArgs的財產傳遞給ReceiveMessageFromAsync中的回調)。該對象將包含收到數據包的IP地址和接口號。

(注意,此代碼沒有經過測試,爲我所用ReceiveMessageFromAsync而非fakey仿開始/結束通話。)

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)作爲建立的一部分之前Bindit。 The ... ReceiveMessageFrom ...方法會爲你設置它,但你可能只會看到Windows設置選項後看到的任何包的有效信息。 (實際上,這並不是什麼大問題 - 但是如果它發生了,原因將會是一個謎,更好的辦法是完全防止它。)

+0

它現在已經過測試,它的工作。這是迄今爲止關於此主題的幾個問題的最佳答案。我很想看到ReceiveMessageFromAsync版本,但如果你沒有回答,可能可以解決它。 – 2014-09-06 21:32:27

+0

@StephenKennedy:說實話,我不認爲我已經有了這樣的代碼了。 :P這並不複雜,但是,IIRC;只需設置一個'SocketAsyncEventArgs',將一個處理程序附加到其Completed事件,然後將該對象傳遞給ReceiveMessageFromAsync。 – cHao 2014-09-06 22:19:49

+0

不用擔心。我有SendToAsync()工作,所以可能與ReceiveMessageFromAsync()tmw :)發揮感謝。 – 2014-09-06 22:53:04

0

我認爲如果你綁定到127.0.0.1而不是IPAddress.Any你會得到你想要的行爲。

0.0.0.0故意表示「每個可用的IP地址」,並且由於綁定語句的原因,它會非常直接地使用它。

+1

雖然爲true,但我不想綁定到127.0.0.1(或任何其他特定的IP地址)。我很抱歉,如果我不清楚,但理想情況下,我想在一臺帶有多個網絡適配器的計算機上運行此UDP服務器(監聽所有這些適配器),並且我想知道該數據包發送給哪一個。 – chezy525 2010-11-10 18:56:27

-1

亞當

這不是測試......讓我們嘗試

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

謝謝,但我相當肯定這段代碼不會工作,甚至編譯...你不能結束一個異步接受調用(Socket.EndAccept)沒有首先開始一個(Socket.BeginAccept),並接受doesn'在無連接套接字上沒有意義。此外,Socket.EndReceiveFrom調用需要對EndPoint的引用,而不是Socket。 – chezy525 2010-11-10 19:53:51

0

的一種方式,你會得到從套接字的地址將其連接到發送者。 一旦你這樣做了,你就可以得到本地地址(或者至少有一個可以發送給發送者),但是,你只能從連接的端點接收消息。

要取消連接,您需要再次使用連接,這次請指定一個家庭地址爲AF_UNSPEC的地址。不幸的是,我不知道在C#中這將如何實現。

(聲明:我從來沒有寫過一行C#,這通常適用於對Winsock)

0

關於緩衝區問題,請嘗試以下操作:

創建一個名爲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

然後,您可以從AsyncState投下StateObject,你正在與udpSock和您的緩衝區做什麼,以及你需要將任何其它數據存儲在那裏。

我想現在唯一的問題是如何以及在哪裏存儲數據,但因爲我不知道你的實現我不能幫助那裏。