Сокет C # UDP: Получить адрес получателя - PullRequest
18 голосов
/ 10 ноября 2010

У меня есть асинхронный серверный класс 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!О, хорошо, я думаю, я пойду с этим, пока не будет что-то лучше.

Ответы [ 5 ]

14 голосов
/ 26 апреля 2011

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

0 голосов
/ 02 мая 2011

В отношении проблемы с буфером попробуйте следующее:

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

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 и вашим буфером, а также любые другие необходимые вам данные, которые будут храниться там.

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

0 голосов
/ 10 ноября 2010

Адам

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

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();
 }
0 голосов
/ 10 ноября 2010

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

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

(Отказ от ответственности: я никогда не писал строки на C #, это относится к Winsock в целом)

0 голосов
/ 10 ноября 2010

Я думаю, что если вы свяжетесь с 127.0.0.1 вместо IPAddress.Any, вы получите желаемое поведение.

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

...