2010-11-10 3 views
17

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

Вот интересные части кода я в настоящее время с помощью:

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

UPDATE

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

ответ

14

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

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

(Обратите внимание, этот код не был проверен, так как я использовал ReceiveMessageFromAsync, а не fakey подделки Начало/завершения вызова.)

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

+0

Он был протестирован сейчас, и он работает. Это, безусловно, лучший ответ на несколько вопросов по этой теме. Я бы очень хотел увидеть версию ReceiveMessageFromAsync, но, возможно, выработаю ее самостоятельно, если вы не ответите. –

+0

@ StephenKennedy: Я не думаю, что у меня даже есть этот код, если честно. : P Это не очень сложно, хотя, IIRC; просто настройте 'SocketAsyncEventArgs', присоедините обработчик к его событию' Completed', затем передайте объект в 'ReceiveMessageFromAsync'. – cHao

+0

Не беспокойтесь. У меня SendToAsync() работает, поэтому может быть игра с ReceiveMessageFromAsync() tmw :) Спасибо. –

0

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

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

+1

Хотя это правда, я не хочу связываться с 127.0.0.1 (или любым другим конкретным IP-адресом). Я прошу прощения, если я был неясен, но в идеале я хочу запустить этот UDP-сервер на компьютере с несколькими сетевыми адаптерами (прослушивая их все), и я хотел бы знать, к какому из них был отправлен пакет. – chezy525

-1

Адам

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

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

Спасибо, но я уверен, что этот код не сработает или даже не скомпилирует ... вы не можете закончить вызов accept async (Socket.EndAccept) без первого начала (Socket.BeginAccept) и принять doesn ' t имеет смысл на разъеме без установления соединения. Кроме того, вызов Socket.EndReceiveFrom требует ссылки на EndPoint, а не на Socket. – chezy525

0

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

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

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

0

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

Создайте класс 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 и буфере, а также anyother данных вам нужно будет там хранится.

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

Смежные вопросы