Подтвердить что ты не робот

С# UDP Socket: Получить адрес получателя

У меня есть асинхронный UDP-сервер с сокетом, связанным с IPAddress.Any, и я хотел бы узнать, какой IP-адрес получил полученный пакет (... или полученный). Похоже, что я не могу просто использовать свойство Socket.LocalEndPoint, так как он всегда возвращает 0.0.0.0 (что имеет смысл, поскольку оно связано с этим...).

Вот интересные части кода, который я использую сейчас:

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

Как упоминалось ранее, это всегда печатает строку типа:

Получено 32 байт от 127.0.0.1:1678 до 0.0.0.0:12345

И я бы хотел, чтобы это было что-то вроде:

Получено 32 байт от 127.0.0.1:1678 до 127.0.0.1:12345

Спасибо, заранее за любые идеи по этому поводу! --Adam

ОБНОВЛЕНИЕ

Ну, я нашел решение, хотя мне это не нравится... В принципе, вместо того, чтобы открывать один udp-сокет, связанный с IPAddress.Any, я создаю уникальный сокет для каждого доступного IPAddress. Итак, новая функция Starter выглядит так:

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! Ну, я думаю, я поеду с этим, пока там что-то лучше.

4b9b3361

Ответ 1

У меня была такая же проблема. Я не вижу способа, используя ReceiveFrom или его асинхронные варианты, чтобы получить адрес назначения принятого пакета.

Однако... Если вы используете ReceiveMessageFrom или его варианты, вы получите IPPacketInformation (по ссылке ReceiveMessageFrom и EndReceiveMessageFrom) или как свойство SocketAsyncEventArgs, переданное вашему обратному вызову в ReceiveMessageFromAsync). Этот объект будет содержать IP-адрес и номер интерфейса, в который был получен пакет.

(Обратите внимание: этот код не был протестирован, так как я использовал ReceiveMessageFromAsync, а не поддельные вызовы Begin/End.)

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) как часть настройки сокета, прежде чем вы Bind его. Метод... ReceiveMessageFrom... установит его для вас, но вы, вероятно, увидите только действительную информацию о любых пакетах, которые видели в Windows после того, как была установлена ​​опция. (На практике это не большая проблема, но когда/если это когда-либо случалось, причина была бы загадкой. Лучше всего это предотвратить.)

Ответ 2

Я думаю, что если вы привязаетесь к 127.0.0.1 вместо IPAddress. Вы получите то, что хотите.

0.0.0.0 преднамеренно означает "каждый доступный IP-адрес", и он воспринимает это буквально как следствие вашего оператора привязки.

Ответ 3

Один из способов получить адрес из этого сокета - связать его с отправителем. Как только вы это сделаете, вы сможете получить локальный адрес (или, по крайней мере, один маршрут для отправителя), однако вы сможете получать сообщения только от подключенной конечной точки.

Чтобы отключиться, вам нужно будет снова использовать соединение, на этот раз указывая адрес с семейством AF_UNSPEC. К сожалению, я не знаю, как это будет достигнуто на С#.

(Отказ от ответственности: я никогда не писал строку С#, это относится к Winsock вообще)

Ответ 4

Что касается проблемы с буфером, попробуйте следующее:

Создайте класс StateObject для хранения любых данных, которые вы хотите иметь в своем обратном вызове, с буфером, также включая сокет, если вам это нужно (поскольку я вижу, что вы в настоящее время передаете udpSock в качестве объекта stateObject). Передайте вновь созданный объект методу async, а затем вы получите доступ к нему в своем обратном вызове.

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

Затем вы можете отбросить StateObject из AsyncState, как вы сейчас делаете с udpSock и вашим буфером, а также любые другие данные, которые вам нужны, будут храниться там.

Я полагаю, что теперь единственная проблема заключается в том, как и где хранить данные, но поскольку я не знаю вашей реализации, я не могу помочь там.

Ответ 5

Адам

Это не проверено... давайте попробуем

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