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

本文关键字:客户端 连接 UDP 之间 服务器 | 更新日期: 2023-09-27 18:22:04

我正在编写基于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);
    }
}
}

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

如果不能访问您的确切环境和网络,我不相信它能有信心提供解决问题的答案。但这里有一些事情需要记住:

  1. 首先,"打孔"并不是一个有技术规范或行业标准支持的定义明确的功能。永远无法保证它会工作,尽管许多路由器的工作方式允许它工作。如果你确信你的代码是正确的,但由于某种原因仍然不工作,那么你总是有可能使用一个或多个路由器,而这些路由器根本无法使用该技术
  2. 根据路由器的行为,客户端从特定端点发送数据报可能足够,也可能不足够。路由器可能不会将外部数据报路由到该端点,直到原始出站数据报的接收方已经回复,即双向通信实际上已经建立。您当前的实现似乎包括允许测试代码的用户在尝试发送到另一个客户端之前等待两个服务器回复的代码,但请确保您正在利用这一点。也就是说,在两个客户端都收到服务器响应之前,您不会尝试从一个客户端发送到另一个客户端
  3. 除上述内容外,服务器向每个客户端发送数据报可能还不够。路由器仍然可以丢弃从未知端点接收的数据报。因此,通常需要的技术的一个变体是,一个客户端尝试向另一个客户端发送数据报,但也通过服务器通知该客户端它已经这样做了(即向服务器发送报告这一情况的数据报,然后服务器向预期的接收方客户端发送数据报知它)

    此数据报将被丢弃,但发送客户端的路由器不知道这一点,因此当另一个客户端直接回复发送客户端时(服务器已经通知它应该回复),现在原始发送客户端的路由器将数据报传递给该客户端。它看到了以前为另一个客户端准备的数据报,因此它将来自该另一个客户机的入站数据报视为有效。从那时起,两个客户端都应该能够直接发送给对方。当然,实现这一点需要一个更复杂的应用程序协议,而不仅仅是像这里的示例那样转发IP地址
  4. 您的代码示例使用SocketOptionName.ReuseAddress,这几乎总是错误的。在我看来,只有你的服务器套接字绑定到一个显式地址,所以使用这个选项可能不会影响测试的结果(即,即使你在一台机器上测试,你在任何给定的地址上仍然只有一个套接字)。但是,如果您的测试环境中有更多的内容,比如套接字地址真的被重用,那么很容易干扰代码的正确操作

最后,您在评论中问道(请将相关信息和问题放在问题中):"我应该使用udp.connect连接到服务器,然后在客户端之间使用udp.send吗"。答案是"不"。首先,在UDP套接字上使用Connect()只是一种方便;UDP本身是无连接的,因此连接UDP套接字完全在框架内处理。其次,在.NET中,当你"连接"UDP套接字时,框架会过滤数据报,将它们限制为从"连接"的端点接收的数据报。这与你想要的打孔正好相反;即,您希望能够从服务器和另一个客户端接收数据报。