19

IPAddress.Anyにソケットがバインドされた非同期UDPサーバークラスがあり、受信したパケットがどの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.
}

前述のように、これは常に次のような行を出力します。

127.0.0.1:1678から0.0.0.0:12345まで32バイトを受信しました

そして、私はそれが次のようなものになりたいです:

127.0.0.1:1678から127.0.0.1:12345まで32バイトを受信しました

事前に、これに関するアイデアをありがとう! - アダム

アップデート

さて、私はそれが好きではありませんが、解決策を見つけました...基本的に、IPAddress.Anyにバインドされた単一のudpソケットを開く代わりに、使用可能なすべてのIPAddressに対して一意のソケットを作成します。したがって、新しいスターター関数は次のようになります。

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パケットヘッダーの一部です。まあ、もっと良いものができるまでこれで行くと思います。

4

5 に答える 5

17

私はちょうど同じ問題を抱えていました。ReceiveFrom受信したパケットの宛先アドレスを取得する方法、またはその非同期バリアントを使用する方法がわかりません。

ただしReceiveMessageFrom、またはそのバリアントを使用する場合は、 (およびのIPPacketInformation参照によって、またはのコールバックに渡されるプロパティとして)を取得します。そのオブジェクトには、パケットが受信されたIPアドレスとインターフェイス番号が含まれます。ReceiveMessageFromEndReceiveMessageFromSocketAsyncEventArgsReceiveMessageFromAsync

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)、ソケットの設定の一部として呼び出す必要があることに注意してください。... ReceiveMessageFrom ...メソッドによって設定されますが、オプションが設定された後にWindowsが検出したパケットについてのみ有効な情報が表示される可能性があります。(実際には、これはそれほど問題ではありませんが、発生した場合、その理由は謎になります。完全に防止することをお勧めします。) Bind

于 2011-04-26T19:50:51.300 に答える
1

バッファの問題に関しては、次のことを試してください。

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

次に、udpSockとバッファで現在行っているように、AsyncStateからStateObjectをキャストできます。また、必要なその他のデータもそこに保存されます。

今のところ唯一の問題はデータをどこにどのように保存するかだと思いますが、あなたの実装がわからないので、そこでは仕方がありません。

于 2011-05-02T19:19:15.137 に答える
0

IPAddress.Anyの代わりに127.0.0.1にバインドすると、希望する動作が得られると思います。

0.0.0.0は、意図的に「使用可能なすべてのIPアドレス」を意味し、bindステートメントの結果として非常に文字通りに解釈されます。

于 2010-11-10T18:46:31.510 に答える
0

そのソケットからアドレスを取得する1つの方法は、それを送信者に接続することです。これを行うと、ローカルアドレス(または少なくとも1つは送信者にルーティング可能)を取得できますが、接続されたエンドポイントからのみメッセージを受信できます。

接続を解除するには、もう一度接続を使用する必要があります。今回は、のファミリでアドレスを指定しますAF_UNSPEC。残念ながら、これがC#でどのように実現されるかはわかりません。

(免責事項:私はC#の行を書いたことがありません、これは一般的にWinsockに適用されます)

于 2010-11-10T19:01:43.547 に答える
-2

アダム

これはテストされていません...試してみましょう

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();
 }
于 2010-11-10T19:01:31.130 に答える