2015-11-14 2 views
2

Я пишу приложение, основанное на UDP Hole Punching. У меня проблема с установлением связи между клиентами. После того как каждый клиент отправляет что-то на серверные и серверные ответы друг другу с помощью своих IP-адресов, клиенты не могут ничего отправлять друг другу. Я что-то пропустил? Или мое понимание UDP Hole Punching неверно? Да, у меня внешний IP для ПК, где есть сервер.клиентский сервер UDP-соединение между клиентами C#

код сервера:

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

    } 
} 
} 
+0

Должен ли я использовать udp.connect на сервере, а затем udp.send между клиентами? – KKKk

ответ

0

Без доступа к вашей точной среде и сети, я не уверен, что это будет можно было бы дать ответ с уверенностью, что он будет уверен в решении этой проблемы. Но вот что-то нужно иметь в виду:

  1. Прежде всего, «дырокол» не является четко определенной функцией с технической спецификацией или промышленным стандартом для поддержки. Никогда не было никаких гарантий, что он будет работать, хотя, конечно, многие маршрутизаторы работают таким образом, чтобы это позволяло. Если вы уверены, что ваш код верен, но по какой-то причине все еще не работает, всегда возможно, что вы используете один или несколько маршрутизаторов, которые просто не будут работать с этой техникой.
  2. В зависимости от поведения маршрутизатора может быть или не быть достаточным для того, чтобы клиент отправил дейтаграмму из определенной конечной точки. Маршрутизатор не может маршрутизировать внешние датаграммы в эту конечную точку до тех пор, пока не ответит получатель исходной исходящей датаграммы, т. Е. Фактически установлено двустороннее сообщение. Ваша текущая реализация включает в себя код, позволяющий пользователю тестировать код дождаться ответов на сервер, прежде чем пытаться отправить другому клиенту, но убедитесь, что вы это воспользовались. То есть что вы не пытаетесь отправлять от одного клиента другому, пока оба клиента не получили ответ сервера.
  3. В дополнение к вышесказанному может быть недостаточно, чтобы сервер отправлял дейтаграммы каждому клиенту. Маршрутизатор может все же отказаться от датаграмм, полученных от неизвестной конечной точки. Таким образом, вариация техники, которая часто требуется, заключается в том, что один клиент предпринимает попытки отправить дейтаграмму другому клиенту, но также уведомляет об этом клиента через сервер, который он сделал (т.е. отправляет серверу датаграмму, сообщающую об этом, и то сервер отправляет датаграмму для предполагаемого клиента-получателя, чтобы уведомить об этом).

    Эта датаграмма будет отброшена, но маршрутизатор отправляющего клиента этого не знает, поэтому, когда другой клиент отвечает непосредственно отправляющему клиенту (был уведомлен сервером, что он должен), теперь маршрутизатор исходного почтового клиента передаст дейтаграмму этому клиенту. Он видел предыдущую дейтаграмму, предназначенную для этого другого клиента, поэтому он обрабатывает входящую дейтаграмму от этого другого клиента как действительную. С этого момента оба клиента должны иметь возможность отправлять друг другу напрямую. Естественно, для реализации этого требуется более сложный протокол приложения, чем просто пересылка IP-адресов в качестве примера.
  4. В ваших примерах кода используется SocketOptionName.ReuseAddress, что почти всегда неправильно. Мне кажется, что только ваш сокет сервера привязывается к явному адресу, поэтому использование этой опции, вероятно, не повлияет на результат теста (т. Е. У вас по-прежнему есть только один сокет на любом заданном адресе, даже если вы тестируете в одном машина). Но если в вашей тестовой среде больше, так что адрес сокета действительно используется повторно, что может легко помешать правильной работе кода.

Наконец, вы спросите в комментариях (пожалуйста, поставьте соответствующую информацию и вопросы в вопрос себя): «я должен использовать udp.connect на сервер, а затем udp.send между клиентами». Ответ - нет". Прежде всего, использование Connect() на сокет UDP - это просто удобство; Сам UDP не требует установления соединения, поэтому подключение UDP-сокета осуществляется полностью в рамках. Во-вторых, в .NET, когда вы «подключаете» UDP-сокет, инфраструктура будет фильтровать датаграммы, ограничивая их теми, которые получены от «подключенной» конечной точки. Это точно противоположно тому, что вы хотите с отверстием; то есть вы хотите иметь возможность получать датаграммы как с сервера, так и с другого клиента.

+0

Идея этого приложения заключается в том, чтобы работать как TeamViewer, просто говоря, tommorow. Я проверю ваши предложения, спасибо за ответ. – KKKk