简体   繁体   English

客户端之间的客户端服务器UDP连接c#

[英]client server UDP connection between clients c#

I'm writing application which is based on UDP Hole Punching. 我正在写基于UDP打孔的应用程序。 I have a problem with establishing connection between clients. 我在建立客户端之间的连接时遇到问题。 After each client sends something to server and server responses to each other with their IPs, clients aren't able to send anything to each other. 在每个客户端向服务器发送一些内容以及服务器使用其IP相互响应之后,客户端将无法相互发送任何内容。 Am I missing anything? 我有什么想念的吗? Or my understanding of UDP Hole Punching is wrong? 还是我对UDP打孔的理解是错误的? Yes, I've external IP for PC where server is. 是的,我有服务器所在的PC的外部IP。

server code : 服务器代码:

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

    }
}
}

client 1: 客户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));
    }
}
}

client 2: 客户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);

    }
}
}

Without access to your exact environment and network, I am not convinced it will be possible to offer an answer with any confidence that it would be assured of addressing the issue. 如果无法访问您的确切环境和网络,我不相信有信心提供肯定会解决该问题的答案。 But here are something things to keep in mind: 但是,请记住以下几点:

  1. First and foremost, "hole punching" is not a well-defined feature with a technical specification or industry standard for support. 首先,“打孔”不是具有技术规范或行业标准支持的明确定义的功能。 There is never any guarantee that it will work, though of course many routers work in a way that allows it to. 尽管有很多路由器以允许它运行的方式工作,但是它永远不能保证它会运行。 If you are sure your code is correct but is for some reason still not working, it is always possible that you are using one or more routers that simply won't work with the technique. 如果您确定自己的代码正确无误,但由于某种原因仍然无法使用,则很可能使用的是一个或多个无法使用该技术的路由器。
  2. Depending on the router behavior, it may or may not be sufficient for a client to have sent a datagram from a specific endpoint. 根据路由器的行为,客户端从特定端点发送数据报可能不够,也可能不够。 A router may not route foreign datagrams to that endpoint until the recipient of the original outbound datagram has replied, ie two-way communication has in fact been established. 路由器可能不会将外部数据报路由到该端点,直到原始出站数据报的接收者已答复,即实际上已经建立了双向通信。 Your current implementation appears to include code to allow the user testing the code to wait for both server replies before attempting to send to the other client, but do make sure you're taking advantage of that. 您当前的实现似乎包含代码,以允许用户测试该代码以等待两个服务器的回复,然后再尝试将其发送给另一个客户端,但是请确保您利用了这一点。 Ie that you don't try to send from one client to the other until both clients have received the server response. 也就是说,在两个客户端都收到服务器响应之前,您不要尝试从一个客户端发送到另一个客户端。
  3. In addition to the above, it may not be sufficient for the server to have sent datagrams to each client. 除了上述内容外,服务器向每个客户端发送数据报可能还不够。 A router may still discard datagrams received from an unknown endpoint. 路由器可能仍会丢弃从未知端点接收到的数据报。 So a variation on the technique that is often required is that one client attempts to send a datagram to the other client, but also notifies that client via the server that it has done so (ie sends the server a datagram reporting this, and then the server sends a datagram to the intended recipient client to notify it). 因此,通常需要使用的一种技术变化是,一个客户端尝试向另一个客户端发送数据报,但也通过服务器通知该客户端已这样做(即,向服务器发送报告此情况的数据报,然后向服务器发送报告)。服务器将数据报发送到预期的收件人客户端以通知它。

    This datagram will be discarded, but the sending client's router doesn't know this, so when the other client replies directly to the sending client (having been notified by the server that it should), now the original sending client's router will pass the datagram to the that client. 该数据报将被丢弃,但是发送客户端的路由器不知道此信息,因此当其他客户端直接回复发送客户端时(服务器已通知它应该发送), 现在原始发送客户端的路由器将通过数据报给那个客户。 It saw the previous datagram that was intended for that other client, so it treats the inbound datagram from that other client as valid. 它看到了以前打算用于该其他客户端的数据报,因此它将来自该其他客户端的入站数据报视为有效。 From that point forward, both clients should be able to send to each other directly. 从那时起,两个客户端都应该能够直接彼此发送邮件。 Naturally, implementing this requires a more complicated application protocol than just forwarding IP addresses as your example here does. 自然,实现此功能需要比仅转发IP地址更复杂的应用程序协议,如您的示例所示。
  4. Your code examples use SocketOptionName.ReuseAddress , which is almost always wrong. 您的代码示例使用SocketOptionName.ReuseAddress ,这几乎总是错误的。 It appears to me that only your server socket binds to an explicit address, so using this option probably wouldn't affect the outcome of the test (ie you still only have one socket on any given address, even if you are testing in a single machine). 在我看来,只有您的服务器套接字绑定到显式地址,因此使用此选项可能不会影响测试结果(即,即使在单个地址上进行测试,您在给定的地址上仍然只有一个套接字)机)。 But if there is more to your testing environment, such that the socket address really is being reused, that can easily interfere with the correct operation of the code. 但是,如果您的测试环境还有更多问题,例如确实要重用套接字地址,那么很容易会干扰代码的正确操作。

Finally, you ask in a comment (please put relevant information and questions in the question itself): "should I use udp.connect to the server and then udp.send between clients" . 最后,您在评论中提问(请在问题本身中放入相关信息和问题): “我应该使用udp.connect到服务器,然后在客户端之间使用udp.send” The answer is "no". 答案是不”。 First of all, using Connect() on a UDP socket is merely a convenience; 首先,在UDP套接字上使用Connect()只是一种方便; UDP itself is connectionless, and so connecting a UDP socket is handled entirely within the framework. UDP本身是无连接的,因此连接UDP套接字的操作完全在框架内进行。 Second, in .NET when you "connect" a UDP socket, the framework will filter datagrams, restricting them to those received from the "connected" endpoint. 其次,在.NET中,当您“连接” UDP套接字时,框架将过滤数据报,并将其限制为从“已连接”端点接收到的数据报。 This is exactly the opposite of what you want with hole-punching; 这与您想要的打孔恰好相反。 ie you want to be able to receive datagrams from both the server and the other client. 也就是说,您希望能够从服务器和另一个客户端接收数据报。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM