【问题标题】:Using a java socket from JNI / C++ code使用 JNI / C++ 代码中的 java 套接字
【发布时间】:2021-05-02 02:30:19
【问题描述】:

我有一个 java 应用程序,它创建一个套接字来与服务器进程通信,例如 new java.net.Socket(String host, int port)。这个应用程序包含一堆遗留的 c++ 代码,这些代码需要从该服务器中获取大量数据并进行处理。目前这是通过让本机代码创建自己的套接字并连接到服务器来实现的,例如:

sock = socket(AF_INET, SOCK_STREAM, 0);
struct hostent* hp = gethostbyname(host);
if (!hp)
{
  unsigned long addr = inet_addr(host);
  hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
}

struct sockaddr_in name;
name.sin_family = AF_INET;
memcpy(&name.sin_addr, hp->h_addr, hp->h_length);
name.sin_port = htons(port);

connect(sock, (sockaddr*)&name, sizeof(name));

在具有多个 NIC(例如有线和 wifi 或 vpn 连接)的 Windows vista/7 机器上,这两个套接字最终可能具有不同的本地地址。 java 代码似乎选择了一个“更好”的接口(有线 Gb enet = 更高的 MTU?),本机(天真?)代码获得了“默认”接口(插入 USB wifi 设备,它成为您的默认接口 - 呸) .

这给我带来了一些问题,我认为细节不相关。两个问题:

  1. 我是否可以重用 JNI 代码中的 java 套接字(可移植?假设是 Sun JDK)。这将完全避免这个问题,但到目前为止,我还没有看到任何与 JNI/native 代码中的 java.net.Socket 内容交互的方法。

  2. 既然第一个问题的答案可能是“否”,那么 java 是如何创建该套接字(选择接口)的?欢迎使用代码 sn-ps。我在 openjdk 的东西中四处寻找,但没有找到我要找的东西。

谢谢, 克里斯

【问题讨论】:

    标签: java c++ sockets java-native-interface


    【解决方案1】:

    回答您的第一个问题:是否可以在本机代码中重用 Java 的套接字 - 是的,但我不建议这样做(您会将自己与特定实现/版本的内部联系起来);但如果你真的必须:使用反射来访问 java.net.SocketImpl 上的 java.io.FileDescriptor,然后使用 sun.misc。 JavaIOFileDescriptorAccess 的 get 方法获取本机套接字描述符。结帐DualStackPlainSocketImpl.java)

    回答你的第二个问题:在 Windows 上查找默认接口的 Java 算法是什么——查看net_util_md.c 中的 getDefaultIPv6Interface 方法(不要让 v6 愚弄你——我相信它也用于 v4)。

    我建议您从 C (JNI) 代码或 Java 代码打开并使用套接字,最好是后者,因为您会发现清理和错误处理最好在管理插座。在 Java 中打开套接字并从 C (JNI) 传递字节缓冲区的想法是完全理智的,您不应该在 JNI 代码中发现合理缓冲区大小和正确释放的堆问题。

    想想可以毫不费力地处理大量数据的 Java 应用程序服务器。

    【讨论】:

    • 谢谢Zoran,我去看看。
    • -1 建议在私有 JVM 实现细节中搞混。无需诉诸此类暴行就可以做到这一点
    • 你读过这个问题,我的回答了吗?也许您想提供一个解决方案,而不需要访问私有 JVM 实现细节?
    • “net_util_md.c”的存储库不再可用。
    【解决方案2】:

    对于您的第一个问题,如果您的 JNI 代码中有 java.net.Socket 对象引用,您可以在其上调用方法,这样您就可以通过套接字读取和写入数据。

    【讨论】:

      【解决方案3】:

      注意在 JVM 实现细节中出现的解决方案,它们很可能在未来或与不同供应商的 VM 一起出现故障。有一种方法可以使用java.nio API 以可移植的方式执行此操作。有一些方法可以从本机代码与通道通信,而无需将缓冲区复制到 Java 堆或从 Java 堆复制缓冲区。

      基本的想法是在你的java代码中创建一个java.nio.SocketChannel来打开连接。然后在 C++ 中使用NewDirectByteBuffer 创建一个java.nio.ByteBuffer 实例,该实例可以传递给通道实例的read/write 方法。

      查看JNI Enhancements Introduced in Version 1.4 of the Java 2 SDKNew I/O APIs 了解详情。

      【讨论】:

        【解决方案4】:

        你的第二个问题-

        您可以随时“绑定”到您想要的本地接口(只需要它的 IP 地址)

        public void bind(SocketAddress addr) throws SocketException 将此 DatagramSocket 绑定到特定地址和端口

        【讨论】:

        • 在 Java 中调用 bind 听起来并没有什么帮助——它已经选择了最好的接口。但是获取 Java 套接字的本地地址,然后在本机端调用 bind 以使用相同的接口,这听起来是一种合理的方法。
        • Ben,这实际上是我所做的,但我有一个本地套接字无法连接到服务器的报告(在将其绑定到与 java 套接字相同的本地地址之后)。我还没弄清楚为什么会失败,这就是我想看看java实现的原因。
        【解决方案5】:

        我想不出为什么 Java 会选择比原生代码“更好”的本地接口。它所做的只是调用本机代码,与您自己的代码非常相似。您可能会看到一些依赖于顺序而不是依赖于 Java 的东西。

        【讨论】:

        • 我能想到几个原因,但我无法说出真正的原始原因是什么。然而,正如 Zoran 所指出的,行为是不同的。 java 代码使用 SIO_ROUTING_INTERFACE_QUERY 调用 WSAIoctl 来获取接口。在这一点上,无论如何我都倾向于完全摆脱原生套接字代码。
        • 是的,一切都来自伯克利套接字。
        【解决方案6】:

        您可以基于 JNI 函数提供带有自己的 FileDescriptor 的 SocketImpl 子类,该函数公开本机套接字。正如您在 JNI 中一样,您无论如何都必须考虑平台(-抽象),但您独立于 Java 实现,根据公认的答案,这是主要风险。

        您的 SocketImpl 类由 Socket.setSocketImplFactory() 部署,并带有合适的工厂。

        【讨论】:

          猜你喜欢
          • 2011-09-07
          • 1970-01-01
          • 1970-01-01
          • 2011-02-15
          • 1970-01-01
          • 2014-01-19
          • 2012-06-26
          • 2017-11-30
          • 1970-01-01
          相关资源
          最近更新 更多