2010-11-10 10 views
17

Mam asynchroniczną klasę serwera UDP z gniazdem powiązanym z adresem IPAddress.Any, i chciałbym wiedzieć, do którego adresu IP odebrany pakiet został wysłany (... lub odebrany). Wydaje się, że nie mogę po prostu użyć właściwości Socket.LocalEndPoint, ponieważ zawsze zwraca ona wartość 0.0.0.0 (co ma sens, ponieważ jest związana z tym ...).C# UDP Socket: Uzyskaj adres odbiornika

Oto interesujących części kodu obecnie używam:

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

Jak wspomniano wcześniej, to zawsze drukuje taką linię:

Odebrane 32 bajtów z 127.0.0.1:1678 do 0.0.0.0: 12345

I chciałbym to być coś takiego:

Odebrane 32 bajtów z 127.0.0.1:1678 do 127.0.0.1:12345

Dzięki, z góry za wszelkie pomysły na ten temat! --Adam

UPDATE

Cóż, znalazłem rozwiązanie, choć nie lubię go ... Zasadniczo, zamiast otwarcie jednego gniazda udp związany IPAddress.Any, tworzę unikalne gniazdo dla każdego dostępnego adresu IP. Tak więc, nowa funkcja Starter wygląda następująco:

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

To jest tylko do zilustrowania koncepcji, największy problem z tym kodem, jak jest to, że każde gniazdo stara się wykorzystać ten sam bufor ... co jest generalnie zły pomysł ...

Musi być lepsze rozwiązanie tego; Mam na myśli, że źródło i miejsce docelowe są częścią nagłówka pakietu UDP! No cóż, chyba pójdę z tym, dopóki nie będzie czegoś lepszego.

Odpowiedz

14

prostu miałem ten sam problem. Nie widzę sposobu, używając adresu ReceiveFrom lub jego asynchronicznych wariantów, aby pobrać adres docelowy odebranego pakietu.

Jednak ...Jeśli użyjesz ReceiveMessageFrom lub jego wariantów, otrzymasz numer IPPacketInformation (w odniesieniu do ReceiveMessageFrom i EndReceiveMessageFrom lub jako właściwość SocketAsyncEventArgs przekazaną do Twojego wywołania zwrotnego w ReceiveMessageFromAsync). Obiekt ten będzie zawierał adres IP i numer interfejsu, na którym pakiet został odebrany.

(Uwaga, kod ten nie został przetestowany, jak kiedyś ReceiveMessageFromAsync zamiast fakey-fałszywe kroki/kończenia połączeń.)

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

Uwaga, należy podobno nazywają SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true) jako część ustawień w górę gniazdo, przed TobąBindit. Metody ... ReceiveMessageFrom ... ustawią ją dla ciebie, ale prawdopodobnie zobaczysz tylko prawidłowe informacje na temat pakietów, które Windows zobaczył po ustawieniu opcji. (W praktyce nie stanowi to większego problemu - ale kiedy to się stanie, przyczyna byłaby tajemnicą.) Lepiej, aby temu zapobiec.)

+0

Zostało przetestowane teraz mate i działa. Jest to zdecydowanie najlepsza odpowiedź na kilka pytań na ten temat. Chciałbym zobaczyć wersję ReceiveMessageFromAsync, ale prawdopodobnie nie rozwiążę tego, jeśli nie odpowiesz. –

+0

@StephenKennedy: Nie sądzę, żebym już miał ten kod, mówiąc szczerze. : P To nie jest strasznie skomplikowane, IIRC; po prostu skonfiguruj 'SocketAsyncEventArgs', dołącz program obsługi do zdarzenia 'Completed', a następnie przekaż obiekt do' ReceiveMessageFromAsync'. – cHao

+0

Bez obaw. Mam funkcję SendToAsync(), więc mogę mieć grę z ReceiveMessageFromAsync() tmw :) Dzięki. –

0

Myślę, że jeśli powiążesz 127.0.0.1 zamiast IPAddress.Any dostaniesz zachowanie, które chcesz.

0.0.0.0 celowo oznacza "każdy dostępny adres IP" i bierze to bardzo dosłownie w konsekwencji oświadczenia o powiązaniu.

+1

Chociaż nie, nie chcę wiązać z 127.0.0.1 (ani żadnym innym określonym adresem IP). Przepraszam, jeśli byłem niejasny, ale najlepiej chcę uruchomić ten serwer UDP na komputerze z wieloma kartami sieciowymi (słuchając ich wszystkich) i chciałbym się dowiedzieć, do którego z nich został wysłany pakiet. – chezy525

-1

Adam

To nie jest testowany ... spróbujmy

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

Dzięki, ale jestem prawie pewien, że ten kod nie będzie działał, ani nawet skompilował ... nie można zakończyć asynchronicznego akceptowania połączenia (Socket.EndAccept) bez pierwszego początku (Socket.BeginAccept), a akceptowanie nie ' t sens na gniazdu bezpołączeniowym. Ponadto wywołanie Socket.EndReceiveFrom wymaga odwołania do punktu końcowego, a nie do gniazda. – chezy525

0

Jednym ze sposobów dostaniesz adres z tego gniazda byłoby podłączyć go do nadawcy. Gdy to zrobisz, będziesz mógł uzyskać adres lokalny (lub co najmniej jeden routing do nadawcy), ale będziesz mógł odbierać wiadomości tylko z podłączonego punktu końcowego.

Aby rozłączyć, musisz ponownie użyć połączenia, tym razem podając adres z rodziną AF_UNSPEC. Niestety, nie wiem jak to osiągnąć w C#.

(Zastrzeżenie: Nigdy nie napisałem wiersza C#, dotyczy to Winsock w ogóle)

0

W odniesieniu do problemu z buforem spróbuj wykonać następujące czynności:

Utwórz klasę o nazwie StateObject, aby przechowywać wszystkie dane, które chcesz mieć w swoim oddzwanianiu, z buforem, w tym także z gniazdem, jeśli tego potrzebujesz (ponieważ widzę, że aktualnie przekazujesz udpSock jako swój obiekt stanowy). Przekaż nowo utworzony obiekt do metody asynchronicznej, a następnie uzyskasz do niego dostęp w ramach wywołania zwrotnego.

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

W poszukiwaniu to pytanie znalazłem:

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

Następnie można rzucić StateObject z AsyncState jak jesteś aktualnie robi z udpSock i swoim buforze, jak również anyother danych trzeba będzie być tam przechowywane.

Podejrzewam, że obecnie jedynym problemem jest sposób i miejsce przechowywania danych, ale ponieważ nie znam Twojej implementacji, nie mogę tam pomóc.

Powiązane problemy