ejemplo - socket c# example
Socket C#UDP: Obtener la dirección del receptor (5)
Acabo de tener el mismo problema. No veo una forma, utilizando 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á una IPPacketInformation
(por referencia para ReceiveMessageFrom
y EndReceiveMessageFrom
, o como una propiedad de SocketAsyncEventArgs
transferida 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.
(Tenga en cuenta que este código no se ha probado, ya que usé ReceiveMessageFromAsync
lugar de las llamadas de inicio / finalización falsas).
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
);
}
Tenga en cuenta que, aparentemente, debería llamar a SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true)
como parte de la configuración del socket, antes de Bind
. Los métodos ... ReceiveMessageFrom ... lo establecerán por usted, pero probablemente solo verá información válida en cualquier paquete que Windows haya visto después de que se configuró la opción. (En la práctica, esto no es un gran problema, pero cuando / si alguna vez sucedió, la razón sería un misterio. Es mejor prevenirlo por completo).
Tengo una clase de servidor UDP asíncrono con un socket vinculado a IPAddress.Any, y me gustaría saber a qué dirección IP se envió el paquete recibido (... o recibido el). Parece que no puedo usar la propiedad Socket.LocalEndPoint, ya que siempre devuelve 0.0.0.0 (lo que tiene sentido, ya que está vinculado a eso ...).
Aquí están 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, esto siempre imprime una línea como:
Recibió 32 bytes de 127.0.0.1:1678 a 0.0.0.0:12345
Y me gustaría que fuera algo como:
Recibió 32 bytes de 127.0.0.1:1678 a 127.0.0.1:12345
Gracias, de antemano, por cualquier idea sobre esto! --Adán
ACTUALIZAR
Bueno, encontré una solución, aunque no me gusta ... Básicamente, en lugar de abrir un solo socket udp vinculado a IPAddress. En cualquier caso, creo un socket único para cada dirección IP disponible. Por lo tanto, la nueva función de inicio se ve como:
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 solo para ilustrar el concepto, el mayor problema con este código tal como está, es que cada socket está tratando de usar el mismo búfer ... lo que generalmente es una mala idea ...
Tiene que haber una mejor solución a esto; Quiero decir, ¡la fuente y el destino son parte del encabezado del paquete UDP! Oh bueno, supongo que seguiré con esto hasta que haya algo mejor.
Adán
Esto no está probado ... intentemos
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();
}
Con respecto al problema del búfer, intente lo siguiente:
Cree una clase llamada StateObject para almacenar cualquier información que quiera tener en su devolución de llamada, con un búfer, que también incluye el socket si lo necesita (ya que veo que actualmente está pasando udpSock como su stateObject). Pase el objeto recién creado al método asíncrono 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);
}
}
Al buscar esta pregunta encontré:
http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.beginreceivefrom.aspx
Luego puede emitir el StateObject desde AsyncState como lo está haciendo actualmente con udpSock y su búfer, así como cualquier otro dato que necesite se almacenará allí.
Supongo que ahora el único problema es cómo y dónde almacenar los datos, pero como no sé su implementación, no puedo ayudarlo.
Creo que si se enlaza a 127.0.0.1 en lugar de a IPAddress. Cualquiera obtendrá el comportamiento que desea.
0.0.0.0 significa deliberadamente "cada dirección IP disponible" y se toma muy literalmente como consecuencia de su declaración de enlace.
Una forma de obtener una dirección de ese socket sería conectarlo al remitente. Una vez que haga eso, podrá obtener la dirección local (o al menos, una enrutable al remitente), sin embargo, solo podrá recibir mensajes del punto extremo conectado.
Para desconectarte, deberás usar conectar de nuevo, esta vez especificando una dirección con una familia de AF_UNSPEC
. Desafortunadamente, no sé cómo se lograría esto en C #.
(Descargo de responsabilidad: nunca he escrito una línea de C #, esto se aplica a Winsock en general)