2010-11-10 20 views
17

Tengo una clase de servidor UDP asíncrono con un socket vinculado a IPAddress.Any, y me gustaría saber a qué IPAddress se envió el paquete recibido (... o recibido). Parece que no puedo usar la propiedad Socket.LocalEndPoint, ya que siempre devuelve 0.0.0.0 (lo cual tiene sentido ya que está vinculado a eso ...).Socket C# UDP: Obtener la dirección del receptor

Estas son las partes interesantes del código que estoy usando actualmente:

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

Como se mencionó anteriormente esta siempre imprime una línea como:

recibido 32 bytes desde 127.0.0.1:1678 en 0.0.0.0: 12345

Y me gustaría que fuera algo así como:

recibido 32 bytes desde 127.0.0.1:1678 a 127.0.0.1:12345

Gracias, de antemano, por alguna idea sobre esto! --Adam

ACTUALIZACIÓN

Bueno, he encontrado una solución, aunque no me gusta ... Básicamente, en lugar de abrir un socket UDP con destino a IPAddress.Any, se crea una toma única para cada dirección IP disponible. Así, la nueva función de arranque se parece a:

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

Esto es sólo para ilustrar el concepto, el mayor problema con este código como está, es que cada socket está intentando utilizar el mismo tampón ... que es generalmente una mala idea ...

Tiene que haber una mejor solución para esto; Quiero decir, la fuente y el destino son parte del encabezado del paquete UDP. Bueno, supongo que seguiré con esto hasta que haya algo mejor.

Respuesta

14

acabo de tener el mismo problema. No veo la forma, usando ReceiveFrom o sus variantes asíncronas, para recuperar la dirección de destino de un paquete recibido.

Sin embargo ...Si usa ReceiveMessageFrom o sus variantes, obtendrá un IPPacketInformation (por referencia para ReceiveMessageFrom y EndReceiveMessageFrom, o como una propiedad del SocketAsyncEventArgs pasado a su devolución de llamada en ReceiveMessageFromAsync). Ese objeto contendrá la dirección IP y el número de interfaz donde se recibió el paquete.

(Nota, este código no ha sido probado, como solía ReceiveMessageFromAsync en lugar de la fakey-falsificación Begin/finalizar llamadas.)

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

Nota, debe parecer llamar SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true) como parte de la creación de la socket, antes que ustedBindes. Los ... métodos ReceiveMessageFrom ... lo configurarán por usted, pero probablemente solo verá información válida sobre cualquier paquete que Windows haya visto después de configurar la opción. (En la práctica, esto no es un gran problema, pero si alguna vez sucedió, la razón sería un misterio. Es mejor evitarlo por completo).

+0

Ha sido probado ahora mate y funciona. Esta es la mejor respuesta a las diversas preguntas sobre este tema. Me gustaría ver la versión de ReceiveMessageFromAsync pero probablemente pueda resolverlo por mí mismo si no respondes. –

+0

@StephenKennedy: para ser sincero, ya no creo tener ese código. : P No es terriblemente complicado, sin embargo, IIRC; simplemente configure un 'SocketAsyncEventArgs', adjunte un controlador a su evento' Completed', luego pase el objeto a 'ReceiveMessageFromAsync'. – cHao

+0

Sin preocupaciones. Tengo SendToAsync() en funcionamiento, así que puedo jugar con ReceiveMessageFromAsync() tmw :) Gracias. –

0

Creo que si se enlaza a 127.0.0.1 en lugar de IPAddress.Any obtendrá el comportamiento que desea.

0.0.0.0 significa deliberadamente "todas las direcciones IP disponibles" y se necesita que sea muy literalmente, como consecuencia de su estado de enlace.

+1

Si bien es cierto, no quiero vincularme a 127.0.0.1 (ni a ninguna otra dirección IP específica). Me disculpo si no estaba claro, pero idealmente, quiero ejecutar este servidor UDP en una máquina con múltiples adaptadores de red (escuchando en todos ellos), y me gustaría saber a cuál se envió el paquete. – chezy525

-1

Adam

Esto no se ha probado ... vamos a tratar

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

Gracias, pero estoy bastante seguro de que este código no funcionará, o incluso compilar ... no puede finalizar una llamada asincrónica (Socket.EndAccept) sin primero iniciar uno (Socket.BeginAccept), y aceptando doesn ' Tiene sentido en una toma de corriente sin conexión. Además, la llamada Socket.EndReceiveFrom requiere una referencia a un EndPoint, no a un Socket. – chezy525

0

Una forma obtendrá una dirección de ese socket sería conectar al remitente. Una vez que lo haga, podrá obtener la dirección local (o al menos, una enrutable para el remitente), sin embargo, solo podrá recibir mensajes del punto final conectado.

Para desconectar, deberá usar connect again, esta vez especificando una dirección con una familia de AF_UNSPEC. Desafortunadamente, no sé cómo se lograría esto en C#.

(Negación: Nunca he escrito una línea de C#, esto se aplica a Winsock en general)

0

En relación con el problema del búfer, intente lo siguiente:

Crea una clase llamada StateObject para almacenar cualquier dato que quieras tener en tu devolución de llamada, con un búfer, incluyendo también el zócalo si lo necesitas (ya que veo que actualmente pasas udpSock como tu stateObject). Pase el objeto recién creado al método async y luego tendrá acceso a él en su devolución de llamada.

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 la búsqueda de esta pregunta que encontré:

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

entonces usted puede echar la StateObject de AsyncState como se está haciendo actualmente con udpSock y su memoria intermedia, así como toda otra información que necesita haría estar almacenado allí.

Supongo que ahora el único problema es cómo y dónde almacenar los datos, pero como no conozco su implementación, no puedo ayudar.

Cuestiones relacionadas