2

UDP ホール パンチングに基づくアプリケーションを作成しています。クライアント間の接続の確立に問題があります。各クライアントがサーバーに何かを送信し、サーバーが IP で相互に応答した後、クライアントは相互に何も送信できなくなります。何か不足していますか?または、UDPホールパンチングの私の理解は間違っていますか? はい、サーバーがある PC の外部 IP があります。

サーバーコード:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.IO;


namespace ConsoleApplication2
{
class Program
{

    static void Main(string[] args)
    {
        IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
        IPEndPoint localEP = new IPEndPoint(IP, 80);
        UdpClient server = new UdpClient();
        server.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        server.ExclusiveAddressUse = false;
        server.Client.Bind(localEP);
        IPEndPoint temp;

        IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 80);

        Console.WriteLine("Dane servera : " + localEP);
        byte[] buffer = server.Receive(ref remoteEP);

        Console.WriteLine("Otrzymano dane od : " + remoteEP + " o treści " + Encoding.ASCII.GetString(buffer));

        temp = remoteEP;

        remoteEP = new IPEndPoint(IPAddress.Any, 80);
        byte[] buffer2 = server.Receive(ref remoteEP);
        Console.WriteLine("Otrzymano dane od : " + remoteEP + " o treści " + Encoding.ASCII.GetString(buffer2));

        byte[] response = Encoding.ASCII.GetBytes(temp.ToString());
        server.Send(response, response.Length, remoteEP);
        byte[] response2 = Encoding.ASCII.GetBytes(remoteEP.ToString());
        server.Send(response2, response2.Length,temp );

    }
}
}

クライアント 1:

namespace ConsoleApplication1
{
class Program
{
    public static IPEndPoint CreateIPEndPoint(string endPoint)
    {
        string[] ep = endPoint.Split(':');
        if (ep.Length < 2) throw new FormatException("Invalid endpoint format");
        IPAddress ip;
        if (ep.Length > 2)
        {
            if (!IPAddress.TryParse(string.Join(":", ep, 0, ep.Length - 1), out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        else
        {
            if (!IPAddress.TryParse(ep[0], out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        int port;
        if (!int.TryParse(ep[ep.Length - 1], NumberStyles.None, NumberFormatInfo.CurrentInfo, out port))
        {
            throw new FormatException("Invalid port");
        }
        return new IPEndPoint(ip, port);
    }
    static void Main(string[] args)
    {
        IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
        IPEndPoint localpt = new IPEndPoint(IP, 80);
        UdpClient client = new UdpClient();
        client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        client.ExclusiveAddressUse = false;
        string powitanie = "ASUS";
        byte[] buffer = new byte[100];
        buffer = Encoding.ASCII.GetBytes(powitanie);
       // client.Connect(localpt);
        client.Send(buffer, buffer.Length,localpt);

        byte[] otrzymane = client.Receive(ref localpt);
        Console.WriteLine("Odpowiedz servera : " + Encoding.ASCII.GetString(otrzymane));
        Console.Read();
        IPEndPoint TV = CreateIPEndPoint(Encoding.ASCII.GetString(otrzymane));


        byte[] buffer2 = client.Receive(ref TV);
       Console.WriteLine("Odpowiedz klienta : " + Encoding.ASCII.GetString(buffer2));
    }
}
}

クライアント 2:

namespace ConsoleApplication1
{
class Program
{
    public static IPEndPoint CreateIPEndPoint(string endPoint)
    {
        string[] ep = endPoint.Split(':');
        if (ep.Length < 2) throw new FormatException("Invalid endpoint format");
        IPAddress ip;
        if (ep.Length > 2)
        {
            if (!IPAddress.TryParse(string.Join(":", ep, 0, ep.Length - 1), out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        else
        {
            if (!IPAddress.TryParse(ep[0], out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        int port;
        if (!int.TryParse(ep[ep.Length - 1], NumberStyles.None, NumberFormatInfo.CurrentInfo, out port))
        {
            throw new FormatException("Invalid port");
        }
        return new IPEndPoint(ip, port);
    }
    static void Main(string[] args)
    {
        IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
        IPEndPoint localpt = new IPEndPoint(IP, 80);
        UdpClient client = new UdpClient();
        client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        client.ExclusiveAddressUse = false;
        string powitanie = "Samsung";
        byte[] buffer = new byte[100];
        buffer = Encoding.ASCII.GetBytes(powitanie);
       // client.Connect(localpt);
        client.Send(buffer, buffer.Length,localpt);

        byte[] otrzymane = client.Receive(ref localpt);
        Console.WriteLine("Odpowiedz servera : " + Encoding.ASCII.GetString(otrzymane));
        Console.Read();
        IPEndPoint TV = CreateIPEndPoint(Encoding.ASCII.GetString(otrzymane));

        client.Send(buffer, buffer.Length, TV);

    }
}
}
4

1 に答える 1

0

あなたの正確な環境とネットワークにアクセスできないと、問題に確実に対処できるという自信を持って回答を提供できるとは思えません。ただし、次の点に注意してください。

  1. 何よりもまず、「ホール パンチング」は、サポートのための技術仕様または業界標準を備えた明確に定義された機能ではありません。もちろん、多くのルーターは動作するように動作しますが、動作するという保証はありません。コードが正しいと確信しているにもかかわらず、何らかの理由でまだ機能しない場合は、その手法では機能しない 1 つ以上のルーターを使用している可能性が常にあります。
  2. ルーターの動作に応じて、クライアントが特定のエンドポイントからデータグラムを送信しただけでは不十分な場合があります。ルーターは、元の送信データグラムの受信者が応答するまで、つまり双方向通信が実際に確立されるまで、外部データグラムをそのエンドポイントにルーティングしない場合があります。現在の実装には、コードをテストするユーザーが他のクライアントに送信する前に両方のサーバーの応答を待機できるようにするコードが含まれているようですが、それを利用していることを確認してください。つまり、両方のクライアントがサーバーの応答を受信するまで、一方のクライアントから他方のクライアントに送信しようとしないでください。
  3. 上記に加えて、サーバーが各クライアントにデータグラムを送信しただけでは不十分な場合があります。ルーターは、不明なエンドポイントから受信したデータグラムを破棄する場合があります。そのため、しばしば必要とされるテクニックのバリエーションは、1 つのクライアントがデータグラムを他のクライアントに送信しようとすることですが、サーバーを介してそのクライアントに送信したことを通知します (つまり、これを報告するデータグラムをサーバーに送信し、その後、サーバーは、目的の受信者クライアントに通知するためにデータグラムを送信します)。

    このデータグラムは破棄されますが、送信側クライアントのルーターはこれを認識していないため、他のクライアントが送信側クライアントに直接応答する場合 (サーバーから通知されているはずです) 元の送信クライアントのルーターは、データグラムをそのクライアントに渡します。その別のクライアントを対象とした以前のデータグラムを検出したため、その別のクライアントからの受信データグラムを有効なものとして扱います。その時点から、両方のクライアントが互いに直接送信できるようになります。当然、これを実装するには、ここでの例のように IP アドレスを転送するだけではなく、より複雑なアプリケーション プロトコルが必要です。
  4. コード例では を使用していますSocketOptionName.ReuseAddressが、これはほとんどの場合間違っています。サーバーソケットのみが明示的なアドレスにバインドされているように見えるので、このオプションを使用してもおそらくテストの結果には影響しません (つまり、単一のソケットでテストしている場合でも、指定されたアドレスにはソケットが 1 つしかありません)。機械)。しかし、ソケット アドレスが実際に再利用されているなど、テスト環境にそれ以上のものがある場合、コードの正しい動作を簡単に妨げる可能性があります。

最後に、コメントで質問します (関連する情報と質問を質問自体に記入してください) 。答えはノーだ"。まず第一にConnect()、UDP ソケットでの使用は単に便利です。UDP 自体はコネクションレスであるため、UDP ソケットの接続は完全にフレームワーク内で処理されます。次に、.NET で UDP ソケットを「接続」すると、フレームワークはデータグラムをフィルタリングし、「接続された」エンドポイントから受信したデータグラムに制限します。これは、穴あけで必要なものとは正反対です。つまり、サーバーと他のクライアントの両方からデータグラムを受信できるようにする必要があります。

于 2015-11-14T20:31:38.170 に答える