2010-11-10 4 views
17

J'ai une classe de serveur UDP asynchrone avec un socket lié à IPAddress.Any, et j'aimerais savoir à quelle IPAddress le paquet reçu a été envoyé (... ou reçu le). Il semble que je ne peux pas simplement utiliser la propriété Socket.LocalEndPoint, car elle renvoie toujours 0.0.0.0 (ce qui est logique car c'est lié à ça ...).Socket UDP C#: Obtenir l'adresse du destinataire

Voici les parties intéressantes du code que je suis actuellement en utilisant:

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

Comme mentionné plus tôt cette affiche toujours une ligne comme:

reçu 32 octets de 127.0.0.1:1678 à 0.0.0.0:

Et je voudrais que ce soit quelque chose comme:

reçu 32 octets de 127.0.0.1:1678 à 127.0.0.1:12345

Merci à l'avance pour toutes les idées sur ce point! --Adam

MISE À JOUR

Eh bien, je trouve une solution, mais je ne l'aime pas ... En fait, au lieu d'ouvrir une seule prise udp liée à IPAddress.Any, je crée une socket unique pour chaque adresse IP disponible. Ainsi, la nouvelle fonction de démarrage ressemble:

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

Ceci est juste pour illustrer le concept, le plus gros problème avec ce code tel qu'il est, est que chaque socket tente d'utiliser le même tampon ... qui est généralement une mauvaise idée ...

Il doit y avoir une meilleure solution à cela; Je veux dire, la source et la destination font partie de l'en-tête de paquet UDP! Eh bien, je suppose que je vais aller avec ça jusqu'à ce qu'il y ait quelque chose de mieux.

Répondre

14

Je viens d'avoir le même problème. Je ne vois pas un moyen, en utilisant ReceiveFrom ou ses variantes asynchrones, pour récupérer l'adresse de destination d'un paquet reçu.

Cependant ...Si vous utilisez ReceiveMessageFrom ou ses variantes, vous obtiendrez un IPPacketInformation (par référence pour ReceiveMessageFrom et EndReceiveMessageFrom, ou comme propriété du SocketAsyncEventArgs passé à votre rappel dans ReceiveMessageFromAsync). Cet objet contiendra l'adresse IP et le numéro d'interface où le paquet a été reçu.

(Note, ce code n'a pas été testé, comme je l'ai utilisé ReceiveMessageFromAsync plutôt que le fakey-faux Begin/mettre fin aux appels.)

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

Remarque, vous devriez apparemment appeler SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true) dans le cadre de la mise en place du socket, avant vousBindil. Les méthodes ... ReceiveMessageFrom ... le définissent pour vous, mais vous ne verrez probablement que des informations valides sur les paquets que Windows a vu après que l'option a été définie. (En pratique, ce n'est pas vraiment un problème - mais quand/si jamais il se produisait, la raison en serait un mystère.Mieux pour l'empêcher complètement.)

+0

Il a été testé maintenant mate et ça marche. C'est de loin la meilleure réponse aux nombreuses questions sur ce sujet. Je serais ravi de voir la version de ReceiveMessageFromAsync mais je peux probablement m'en sortir si vous ne répondez pas. –

+0

@StephenKennedy: Je ne pense même plus avoir ce code, pour être honnête. : P Ce n'est pas très compliqué, cependant, IIRC; configurez simplement un 'SocketAsyncEventArgs', attachez un gestionnaire à son événement' Completed', puis passez l'objet 'ReceiveMessageFromAsync'. – cHao

+0

Pas de soucis. J'ai SendToAsync() fonctionnant ainsi peut avoir un jeu avec ReceiveMessageFromAsync() tmw :) Merci. –

0

Je pense que si vous liez à 127.0.0.1 au lieu de IPAddress.Any vous obtiendrez le comportement que vous voulez. Délibérément signifie "toutes les adresses IP disponibles" et il le prend très littéralement en conséquence de votre instruction bind.

+1

Bien que vrai, je ne veux pas lier à 127.0.0.1 (ou toute autre adresse IP spécifique). Je m'excuse si je n'étais pas clair, mais idéalement, je veux exécuter ce serveur UDP sur une machine avec plusieurs adaptateurs réseau (en les écoutant tous), et j'aimerais savoir à quel paquet le paquet a été envoyé. – chezy525

-1

Adam

Ce n'est pas testé ... essayons

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

Merci, mais je suis assez certain que ce code ne fonctionnera pas, ou même compiler ... vous ne pouvez pas terminer un appel d'acceptation asynchrone (Socket.EndAccept) sans en commencer un (Socket.BeginAccept), et en acceptant doesn ' t faire sens sur une socket sans connexion. En outre, l'appel Socket.EndReceiveFrom nécessite une référence à un point de terminaison, pas un socket. – chezy525

0

Une façon vous obtiendrez une adresse de cette prise serait de le connecter à l'expéditeur. Une fois que vous faites cela, vous serez en mesure d'obtenir l'adresse locale (ou au moins, un routable à l'expéditeur), mais vous ne pourrez recevoir des messages du point de terminaison connecté. Pour vous déconnecter, vous devrez utiliser de nouveau connect, cette fois en spécifiant une adresse avec une famille de AF_UNSPEC. Malheureusement, je ne sais pas comment cela pourrait être réalisé en C#.

(Disclaimer: Je ne l'ai jamais écrit une ligne de C#, cela s'applique à Winsock en général)

0

En ce qui concerne le problème du tampon, essayez ceci:

Créez une classe appelée StateObject pour stocker toutes les données que vous voulez avoir dans votre callback, avec un tampon, y compris le socket si vous en avez besoin (comme je vois que vous passez actuellement udpSock comme stateObject). Passez l'objet nouvellement créé à la méthode async et vous y aurez accès dans votre 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); 
    } 
} 

En cherchant cette question, je trouve:

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

Vous pouvez alors lancer le stateObject de AsyncState que vous faites actuellement avec udpSock et votre tampon, ainsi que anyother données dont vous avez besoin serait être stocké là.

Je suppose que maintenant le seul problème est de savoir comment et où stocker les données, mais comme je ne connais pas votre implémentation, je ne peux rien y faire.

Questions connexes