UDPホールパンチングの実装方法についてよく読んでいますが、何らかの理由で機能させることができません。
udp ホール パンチングが何であるかをよく知らない人のために、私自身の定義を以下に示します。
目標は、サーバーの助けを借りて 2 つのクライアント (クライアント A とクライアント B) 間でデータを転送できるようにすることです。したがって、クライアント A はサーバーに接続し、その情報を送信します。クライアント B も同じことを行います。サーバーには、クライアント A がクライアント B にデータを送信したり、クライアント A からクライアント B にデータを送信したりできるように、必要な情報があります。したがって、サーバーはその情報を両方のクライアントに提供します。両方のクライアントが互いに関する情報を取得すると、サーバーの助けを借りずにそれらのクライアント間でデータの送受信を開始できます。
私の目標は、今説明したこと (udp ホール パンチング) を実行できるようにすることです。その前に、サーバーからクライアントに接続できると便利だと思います。そのために、クライアントに関する情報をサーバーに送信する予定です。サーバーがその情報を受信したら、最初からクライアントに接続しようとします。それを実行できるようになったら、実際の udp ホール パンチングの実装を開始するために必要なものがすべて揃っているはずです。
設定方法は次のとおりです。
上のルーターにはサーバーがあり、下のルーターは LAN ポートに接続されています。下のルーター (NAT) は、WAN ポートを介して上のルーターに接続されています。クライアント コンピュータは、下のルーターの LAN ポートの 1 つに接続されています。
したがって、その接続では、クライアントはサーバーを見ることができますが、サーバーはクライアントを見ることができません。
したがって、擬似コードで行ったアルゴリズムは次のとおりです。
- クライアントがサーバーに接続します。
- クライアントは、NAT でいくつかのポートを開くためにいくつかの UDP パッケージをサーバーに送信します
- クライアントがリッスンしているポートでサーバーに情報を送信します。
- サーバーがその情報を受信したら、最初からクライアントに接続しようとします。
コードでの実装は次のとおりです。
サーバ:
static void Main()
{
/* Part 1 receive data from client */
UdpClient listener = new UdpClient(11000);
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);
string received_data;
byte[] receive_byte_array = listener.Receive(ref groupEP);
received_data = Encoding.ASCII.GetString(receive_byte_array, 0, receive_byte_array.Length);
// get info
var ip = groupEP.Address.ToString();
var port = groupEP.Port;
/* Part 2 atempt to connect to client from scratch */
// now atempt to send data to client from scratch once we have the info
Socket sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint endPointClient = new IPEndPoint(IPAddress.Parse(ip), port);
sendSocket.SendTo(Encoding.ASCII.GetBytes("Hello"), endPointClient);
}
クライアント:
static void Main(string[] args)
{
/* Part 1 send info to server */
Socket sending_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress send_to_address = IPAddress.Parse("192.168.0.132");
IPEndPoint sending_end_point = new IPEndPoint(send_to_address, 11000);
sending_socket.SendTo(Encoding.ASCII.GetBytes("Test"), sending_end_point);
// get info
var port = sending_socket.LocalEndPoint.ToString().Split(':')[1];
/* Part 2 receive data from server */
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, int.Parse(port));
byte[] buffer = new byte[1024];
sending_socket.Receive(buffer);
}
何らかの理由で数回機能しました!クライアントが回線でデータを正常に受信すると機能します。sending_socket.Receive(buffer);
注意事項:listner
2 番目の部分のサーバーで、新しい変数を作成する代わりに
インスタンス変数を使用し、sendSocket
その変数を介してバイトを送信した場合、クライアントは送信されるデータを受信できます。サーバーの 2 番目の部分は 2 番目のクライアント B によって実装されることを思い出してください。そのため、変数を最初から再度初期化しています...
編集:
ここでは、同じ問題を別の方法で見ることができます。同じオブジェクトを使用する代わりに新しいオブジェクトを初期化すると、クライアントは応答を受け取りません。
タイプ UdpClient のオブジェクトがあります。そのオブジェクトでデータを他のピアに送信できます。同じプロパティを持つ同じタイプの別のオブジェクトを作成してデータを送信しようとすると、機能しません! いくつかの変数を初期化するのに欠けている可能性があります。リフレクションでプライベート変数を設定できるので、問題はありません。とにかくここにサーバーコードがあります:
public static void Main()
{
// wait for client to send data
UdpClient listener = new UdpClient(11000);
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);
byte[] receive_byte_array = listener.Receive(ref groupEP);
// connect so that we are able to send data back
listener.Connect(groupEP);
byte[] dataToSend = new byte[] { 1, 2, 3, 4, 5 };
// now let's atempt to reply back
// this part does not work!
UdpClient newClient = CopyUdpClient(listener, groupEP);
newClient.Send(dataToSend, dataToSend.Length);
// this part works!
listener.Send(dataToSend, dataToSend.Length);
}
static UdpClient CopyUdpClient(UdpClient client, IPEndPoint groupEP)
{
var ip = groupEP.Address.ToString();
var port = groupEP.Port;
var newUdpClient = new UdpClient(ip, port);
return newUdpClient;
}
クライアント コードは基本的にサーバーにデータを送信し、応答を待ちます。
string ipOfServer = "192.168.0.132";
int portServerIsListeningOn = 11000;
// send data to server
Socket sending_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress send_to_address = IPAddress.Parse(ipOfServer);
IPEndPoint sending_end_point = new IPEndPoint(send_to_address, portServerIsListeningOn);
sending_socket.SendTo(Encoding.ASCII.GetBytes("Test"), sending_end_point);
// get info
var port = sending_socket.LocalEndPoint.ToString().Split(':')[1];
// now wait for server to send data back
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, int.Parse(port));
byte[] buffer = new byte[1024];
sending_socket.Receive(buffer); // <----- keeps waiting in here :(
クライアントはルーター (NAT) の背後にあることに注意してください。それ以外の場合、この問題は発生しません。udpClient をコピーする理由は、その変数を別のコンピューターに送信して、他のコンピューターがクライアントにデータを送信できるようにするためです。
だから私の質問は 、元のオブジェクトlistener
がデータを送信できるのにできnewClient
ないのはなぜですか? sending_socket.Receive(buffer);
サーバーが行を実行した後でも、クライアントは行で待機し続けます: newClient.Send(dataToSend, dataToSend.Length);
。リスナーがデータを送信しても newClient を送信しない場合、クライアントはデータを正常に受信します。両方の変数の宛先 IP とポートが同じ場合、これはなぜですか? 変数はどのように異なりますか?
注: サーバーとクライアントが同じネットワーク上にある場合、コピーは機能し、変数newClient
はクライアントにデータを送信できます。この問題をシミュレートするには、クライアントが NAT (ルーター) の背後にある必要があります。このようなネットワークの例は、2 つのルーターで構成されている場合があります。それらをルーター X とルーター Y と呼びましょう。S とクライアント C のサーバー呼び出しも必要です。これにより、S は X の LAN ポートの 1 つに接続できます。C は Y の LAN ポートの 1 つに接続できます。最後に、Y の WAN ポートを X の LAN ポートの 1 つに接続します。