NAT 后面的 UDP 打洞

2023-12-25

我正在尝试用 Java 实现 UDP-Holepunching 的简单草图来测试它的概念,并稍后在我的 C/C++ 应用程序中使用它。

Concept:

根据维基百科,我对这个概念的理解是这样的: 假设 A 和 B 是未定义网络结构后面的客户端,C 是众所周知的公共可访问服务器。

  1. A 向服务器 C 发送数据包,服务器保存其 IP 地址和端口。 C 将获得 A 的 NAT 的公共 IP 地址。这样做时,A 前面的 NAT 将创建一条路由,将该端口上的所有数据包传递到 A。
  2. B 与 A 的操作相同,将数据包发送到服务器 C,然后服务器 C 会保存其地址和端口,B 的 NAT 创建路由等等。
  3. 此时,C 知道每个客户端的地址和端口。 C将B的地址和端口发送给A,并从A发送给B。
  4. A 向 B 发送一个数据包,该数据包将被 B 的 NAT 拒绝,但这样做会在 A 的 NAT 中打开一个“漏洞”,让来自 B 的更多数据包通过。
  5. B 向 A 发送一个数据包,该数据包将到达 A,因为之前已“打孔”了一个“洞”。这样做还会在 B 的 NAT 中打开一个“漏洞”,让来自 A 的更多数据包通过。
  6. 打孔现已完成,A 和 B 应该能够相互进行 P2P 通信

这一切都在本地主机上运行良好(这并不是什么大惊喜),但在现实世界的示例中这会失败。

Problem:

A 和 B 都能够连接到服务器 C,服务器 C 获取它们的数据包、存储它们的地址和端口并将其传输到另一个客户端。 但此时它失败了。 A 和 B 无法互相通信。 所以我问自己哪里做错了。我花了几天时间在 google 和 stackoverflow 中搜索工作示例,但我偶然发现的是使用 STUN 的建议,这不是我想要的。

执行:

下面我将用Java发布我的草图,因为我不知道我的概念或我的实现是否有问题。

这是服务器的代码:

public class Server
{
    public static void main(String[] args)
    {
        int port1 = 0, port2 = 0;
        String address1 = null, address2;
        byte[] bytes = new byte[1024];
        try
        {
            System.out.println("Server waiting");
            DatagramSocket ds = new DatagramSocket(789);
            while(!Thread.interrupted())
            {
                DatagramPacket p = new DatagramPacket(bytes, bytes.length);
                ds.receive(p);
                if(port1 == 0)
                {
                    port1 = p.getPort();
                    address1 = p.getAddress().getHostAddress();
                    System.out.println("(1st) Server received:" + new String(bytes) + " from " + address1 + " on port " + port1);
                }
                else
                {
                    port2 = p.getPort();
                    address2 = p.getAddress().getHostAddress();
                    System.out.println("(2nd) Server received:" + new String(bytes) + " from " + address1 + " on port " + port1);
                    sendConnDataTo(address1, port1, address2, port2, ds);
                    sendConnDataTo(address2, port2, address1, port1, ds);
                }
            }
            ds.close();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    public static void sendConnDataTo(String a1, int p1, String a2, int p2, DatagramSocket ds)
    {
        byte[] bA, bP;
        bA = a1.getBytes();
        bP = Integer.toString(p1).getBytes();
        DatagramPacket pck;
        try
        {
            pck = new DatagramPacket(bA, bA.length, InetAddress.getByName(a2), p2);
            ds.send(pck);
            pck = new DatagramPacket(bP, bP.length, InetAddress.getByName(a2), p2);
            ds.send(pck);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}

请注意,这只是一些草图,没有真正的应用。服务器应该只接收来自两个客户端的数据包,保存它们的地址和端口并将其传递给另一个客户端。

这是客户端的代码:

public class Client
{
    private DatagramSocket socket;
    private int init = 0;
    private String target;
    private int port;

    public Client()
    {
        try
        {
            socket = new DatagramSocket();
        }
        catch(SocketException e)
        {
            e.printStackTrace();
        }
        Thread in = new Thread()
        {
            public void run()
            {
                while(true)
                {
                    byte[] bytes = new byte[1024];
                    DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
                    try
                    {
                        socket.receive(packet);
                        bytes = Arrays.copyOfRange(bytes, 0, packet.getLength());
                        String s = new String(bytes);
                        System.out.println("Received: " + s);
                        if(init == 0)
                        {
                            target = s;
                            System.out.println("Target: " + target);
                            init++;
                        }
                        else if(init == 1)
                        {
                            port = Integer.parseInt(s);
                            System.out.println("Port: " + port);
                            init++;
                        }
                        else System.out.println(new String(bytes));
                    }
                    catch(IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        };
        in.start();
        connectToSupervisor();
    }

    private void connectToSupervisor()
    {
        byte[] bytes = new byte[1024];
        System.out.println("Greeting server");
        System.arraycopy("EHLO".getBytes(), 0, bytes, 0, 4);
        try
        {
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("localhost"), 789);
            socket.send(packet);
            System.out.println("Greetings sent...");
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        send();
    }

    private void send()
    {
        while(init != 2)
        {
            try
            {
                Thread.sleep(20L);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        System.out.println("Init completed!");
        while(true)
        {
            byte[] b2 = "Hello".getBytes();
            byte[] b1 = new byte[6];
            System.arraycopy(b2, 0, b1, 0, b2.length);
            try
            {
                DatagramPacket packet = new DatagramPacket(b1, b1.length, InetAddress.getByName(target), port);
                socket.send(packet);
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args)
    {
        new Client();
    }
}

客户端只会向服务器发送一个数据包,侦听来自服务器的数据包,从另一个客户端获取连接数据,然后不断向其发送包含“Hello”的数据包。

我很抱歉代码太长,但我想保持完整。

如果你们中的任何人能够指出我正在犯的错误,向我解释为什么这不起作用,给我一个有效的例子,或者至少给我指出一个替代方案,我会很高兴。


您的代码似乎是正确的。我测试了你的代码,它工作正常。这个概念也是正确的。但请检查您运行的两个客户端是否位于同一 NAT 设备内或不同的 NAT 设备内。如果您在同一 NAT 设备下运行两个客户端,则它可能无法工作,因为并非所有 NAT 设备都支持发夹,即两个客户端都将数据包发送到 NAT 的外部 IP,而该 IP 需要传递给自身。欲了解更多信息,请参阅此链接:https://www.rfc-editor.org/rfc/rfc4787#section-6 https://www.rfc-editor.org/rfc/rfc4787#section-6

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

NAT 后面的 UDP 打洞 的相关文章

随机推荐

  • 如何从SWT表中选择一个单元格

    table addSelectionListener new SelectionAdapter public void widgetSelected SelectionEvent e if table getSelectionIndex 1
  • SQL - 将 2 个外键左连接到 1 个主键

    我有两张桌子 游戏桌和团队桌 我的 sql 语句应该是什么样子来创建一个游戏列表 以提取链接到 TeamID1 和 TeamID2 字段的 TeamName 我相信我可以使用左连接 但我不确定如何处理链接到一个主键的两个外键 非常感谢您提供
  • 从审计记录构建快照表

    我有一个Customer表具有以下结构 CustomerId Name Address Phone 1 Joe 123 Main NULL 我也有一个Audit跟踪更改的表Customer table Id Entity EntityId
  • 我的选择如何正确地出现在我在 SwiftUI 中创建的列表中

    我创建了显示数字数组的列表 当您选择多个数字并点击下面的按钮时 它应该告诉您所选数字的总和 但是 当您在不同行中选择相同的数字时 它们会同时被检查 我知道这应该通过使用 UUID 来解决 但我想要的最终结果是数字之和 即 Int 因此 我迷
  • 我可以从 PriorityQueue 中获取一个项目而不将其删除吗?

    我想获取队列中的下一个项目 但不想将其出列 在Python中可以吗queue PriorityQueue 来自docs http docs python org py3k library queue html highlight prior
  • Firebase 存储的动态图像大小调整

    有没有一种有效的方法可以通过更改图像 URL 中的查询参数来提供存储在 firebase 存储中的动态尺寸图像 或者类似 Imgix 的东西 什么是最有效的方法 我建议您尝试使用 ImageMagick 调整 Firebase 存储大小的
  • 将 gulp.start 函数迁移到 Gulp v4

    我已经将所有 gulp v3 代码库迁移到 v4 中 然而 我陷入了一个困境 我有 gulp start 函数 当我在 gulp v4 中运行 gulp start 时 它会抛出一个错误 这是我在版本 3 中的功能 gulp task de
  • 批处理 for 循环内存泄漏?

    我正在编写一个批处理脚本 它会遍历目录中的每个文件 查找代码文件并以某种方式修改它们 完成该任务后 我尝试在包含大约 6 000 个文件的大目录上运行它 大约 40 分钟的脚本崩溃了 我从命令提示符处收到了很多内存不足的错误 运行脚本的同时
  • 无法将“...”类型的值添加到“UIElementCollection”类型的集合或字典中

    当我通过 XAML 添加自定义控件时 出现以下错误 可能的原因是什么 A value of type cannot be added to a collection or dictionary of type UIElementCollec
  • 如何正确管理屏幕上的精灵组

    我正在用 Swift 构建我的第一个游戏 我想知道如何同时处理多个屏幕精灵 我的游戏将精灵推到屏幕上addChild连续的 所以同时有很多活跃的 我意识到我没有同时影响所有敌人的正确方法 就像我想同时影响所有敌方精灵的物理属性一样 到目前为
  • 简单银行账户的派生账户余额与存储账户余额?

    就像我们普通的银行账户一样 我们有大量的交易 导致资金流入或流出 账户余额始终可以通过简单地总结交易价值来得出 将更新后的账户余额存储在数据库中还是在需要时重新计算 哪一个更好 每个账户的预期交易量 每日 预期检索账户余额 每当交易发生时
  • 如何在 Flutter 中对 JSON 数据进行文本搜索?

    我正在开发一个应用程序 该应用程序需要从 firebase 存储中获取一些 JSON 数据 然后用户可以对其执行搜索 但是 我的搜索查询无法正常工作 并且在多次执行搜索时出现一个错误 我首先将数据上传到 firebase 存储 然后提取它
  • 无法初始化 FirebaseApp - Flutter

    我的项目突然停止正常运行 它无法初始化 FirebaseApp 我一直在使用老式的 Firebase 配置 直接从 Firebase 控制台使用 Google Services 文件 我切换到命令flutterfire configure但
  • 为什么 Git 会在没有询问我的情况下记住并使用中止的 rebase 中的冲突解决方案?

    我对我的分支做了变基foo git rebase master 做了一些冲突解决方案 然后决定我不想这样做 然后中止 git rebase abort 所有分支日志历史看起来都很正常 现在我想再次开始相同的过程 git rebase mas
  • OnClick 事件对列表视图项目重复

    我的应用程序中有一个列表视图 每个列表视图行都会在点击时展开 并且隐藏视图变得可见 但问题是隐藏视图对于列表中的许多其他项目也变得可见 我知道这种行为的原因 但不知道如何解决 这是我的适配器类 public class ScheduleTa
  • 即使没有断点,Chrome 调试器也会暂停?

    检查此站点时 即使没有设置断点 脚本也始终在调试器中暂停 并且如果暂停未暂停 它会再次自行暂停 可以做什么 https i stack imgur com J84eU png https i stack imgur com J84eU pn
  • SELECT MAX 返回错误值

    我有一个 SQL 数据库 其中有一个名为premisis 我希望选择最高的行premisisnumber 这是我正在使用的 SQL SELECT max premisisnr AS premisisnr FROM premisis 问题是
  • 真正的path.py请站起来吗?

    有一个很好的模块 path py 由贾森 奥伦多夫撰写 如果我没记错的话 曾经有过一些关于将其添加到标准库的讨论 然后就消失了 现在看来 原来的产物有多种衍生产物 到目前为止我能找到unipath http pypi python org
  • AWS Beanstalk 在部署 Nodejs 应用程序时如何使用 NPM?

    我对 AWS Beanstalk 部署的整体工作流程感到好奇 我假设它在某个时刻运行 npm 来将软件包安装在服务器上 但我只是想知道 AWS Beanstalk 是否使用最新的 npm install product 命令来安装软件包 目
  • NAT 后面的 UDP 打洞

    我正在尝试用 Java 实现 UDP Holepunching 的简单草图来测试它的概念 并稍后在我的 C C 应用程序中使用它 Concept 根据维基百科 我对这个概念的理解是这样的 假设 A 和 B 是未定义网络结构后面的客户端 C