2010-11-10 14 views
17

Ich habe eine asynchrone UDP-Server-Klasse mit einem Socket auf IPAddress.Any gebunden, und ich möchte wissen, welche IPAddress das empfangene Paket gesendet wurde (... oder empfangen am). Es scheint, dass ich nicht einfach die Socket.LocalEndPoint-Eigenschaft verwenden kann, da es immer 0.0.0.0 zurückgibt (was sinnvoll ist, da es daran gebunden ist ...).C# UDP Socket: Get Empfängeradresse

Hier die interessanten Teile des Codes sind Ich bin derzeit mit:

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. 
} 

Wie bereits erwähnt, dies immer druckt eine Zeile wie:

Received 32 Bytes von 127.0.0.1:1678 bis 0.0.0.0: 12345

Und ich möchte es so etwas wie sein:

Received 32 Bytes von 127.0.0.1:1678 zu 127.0.0.1:12345

Dank im Voraus für alle Ideen auf diesem! --Adam

UPDATE

Nun, ich habe eine Lösung gefunden, obwohl ich es nicht mag ... Im Grunde genommen, anstatt ein einzelnes UDP-Sockets der Öffnung zu IPAddress.Any gebunden, ich erstellen eindeutiger Socket für jede verfügbare IP-Adresse. So sieht die neue Starter-Funktion wie:

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

Das gerade ist das Konzept zu veranschaulichen, das größte Problem mit diesem Code wie es ist, ist, dass jede Steckdose den gleichen Puffer ... verwenden versucht, die in der Regel ist eine schlechte Idee ...

Es muss eine bessere Lösung dafür geben; Ich meine, die Quelle und das Ziel sind Teil des UDP-Paket-Headers! Naja, ich denke, ich werde damit fortfahren, bis es etwas Besseres gibt.

Antwort

14

ich gerade das gleiche Problem hatte. Ich sehe keinen Weg, ReceiveFrom oder seine Async-Varianten zu verwenden, um die Zieladresse eines empfangenen Pakets abzurufen.

Jedoch ...Wenn Sie ReceiveMessageFrom oder seine Varianten verwenden, erhalten Sie eine IPPacketInformation (durch Referenz für ReceiveMessageFrom und EndReceiveMessageFrom, oder als eine Eigenschaft der SocketAsyncEventArgs an Ihren Rückruf in ReceiveMessageFromAsync übergeben). Dieses Objekt enthält die IP-Adresse und die Schnittstellennummer, an der das Paket empfangen wurde.

(Beachten Sie, hat dieser Code nicht getestet worden, wie ich ReceiveMessageFromAsync verwendet, anstatt die fakey-gefälschte Start/Beenden von Anrufen.)

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

Hinweis, sollten Sie offenbar SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true) als Teil aufrufen der Einstellung der Sockel, vor IhnenBindes. Die ... ReceiveMessageFrom ... -Methoden werden es für Sie einstellen, aber Sie werden wahrscheinlich nur gültige Informationen über alle Pakete sehen, die Windows sah, nachdem die Option gesetzt wurde. (In der Praxis ist dies kein großes Problem - aber wenn/falls es je passiert wäre, wäre der Grund ein Rätsel. Besser, es komplett zu verhindern.)

+0

Es wurde jetzt getestet, Partner und es funktioniert. Dies ist bei weitem die beste Antwort auf die verschiedenen Fragen zu diesem Thema. Ich würde gerne die ReceiveMessageFromAsync-Version sehen, aber kann es wahrscheinlich für mich selbst herausfinden, wenn Sie nicht antworten. –

+0

@StephenKennedy: Ich glaube nicht, dass ich diesen Code überhaupt noch habe, um ehrlich zu sein. : P Es ist nicht sehr kompliziert, aber, IIRC; Richten Sie einfach ein 'SocketAsyncEventArgs' ein, fügen Sie einen Handler an sein' Completed' Ereignis an und übergeben Sie das Objekt dann an 'ReceiveMessageFromAsync'. – cHao

+0

Keine Sorge. Ich habe SendToAsync() arbeiten so möglicherweise eine Wiedergabe mit ReceiveMessageFromAsync() tmw :) Danke. –

0

Ich denke, wenn Sie an 127.0.0.1 anstelle von IPAddress.Any binden, erhalten Sie das Verhalten, das Sie wollen.

0.0.0.0 bedeutet bewusst "jede IP-Adresse verfügbar" und es nimmt es sehr wörtlich als eine Konsequenz aus Ihrer Bind-Anweisung.

+1

Obwohl dies der Fall ist, möchte ich nicht an 127.0.0.1 (oder eine andere spezifische IP-Adresse) binden. Ich entschuldige mich, wenn ich unklar war, aber im Idealfall möchte ich diesen UDP-Server auf einem Rechner mit mehreren Netzwerkadaptern laufen lassen (alle abhören), und ich würde gerne wissen, an welches das Paket gesendet wurde. – chezy525

-1

Adam

Dies ist nicht getestet ... versuchen lassen

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

Danke, aber ich bin ziemlich sicher, dass dieser Code nicht funktioniert, oder sogar kompilieren ... Sie können einen Async-Aufruf (Socket.EndAccept) nicht beenden, ohne zuerst einen zu starten (Socket.BeginAccept), und akzeptiert doesn ' Sinnvoll auf einer verbindungslosen Steckdose. Außerdem erfordert der Aufruf Socket.EndReceiveFrom einen Verweis auf einen EndPoint und nicht auf einen Socket. – chezy525

0

Eine Möglichkeit, Sie werden von dieser Buchse eine Adresse zu bekommen wäre es an den Absender zu verbinden. Sobald Sie dies getan haben, können Sie die lokale Adresse abrufen (oder mindestens eine, die an den Absender weitergeleitet werden kann). Sie können jedoch nur Nachrichten vom verbundenen Endpunkt empfangen.

Um die Verbindung zu trennen, müssen Sie die Verbindung erneut herstellen, wobei Sie diesmal eine Adresse mit einer Familie von AF_UNSPEC angeben. Leider weiß ich nicht, wie dies in C# erreicht werden würde.

(Disclaimer: Ich habe noch nie eine Zeile von C# geschrieben, gilt dies im Allgemeinen Winsock)

0

In Bezug auf das Pufferproblem versuchen Sie Folgendes:

Erstellen Sie eine Klasse namens StateObject, um alle Daten, die Sie in Ihrem Rückruf haben möchten, mit einem Puffer, einschließlich der Socket, wenn Sie es benötigen (wie ich sehe, übergeben Sie derzeit UdpSock als stateObject). Übergeben Sie das neu erstellte Objekt an die asynchrone Methode, und Sie haben dann Zugriff auf es in Ihrem Rückruf.

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

diese Frage Bei der Suche fand ich:

http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.beginreceivefrom.aspx

Anschließend können Sie werfen den stateobject von AsyncState wie Sie gerade mit udpSock und Ihre Puffer zu tun, sowie anyother Daten würden Sie brauchen dort gespeichert werden.

Ich nehme an, dass jetzt das einzige Problem ist, wie und wo die Daten zu speichern, aber da ich Ihre Implementierung nicht kenne, kann ich nicht helfen.