使用JNI/C++代码从Java Socket进行通信

10

我有一个Java应用程序,它创建一个socket与服务器进程通信,例如:new java.net.Socket(String host,int port)。这个应用还包括一堆遗留的C++代码,需要从该服务器中提取大量数据并进行处理。目前实现方式是通过本地代码创建自己的socket并连接到服务器,例如:

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));
在具有多个网络接口的Windows vista/7机器上(例如有线和WiFi或VPN连接),这两个套接字可能会得到不同的本地地址。Java代码似乎选择了一个“更好”的接口(有线Gb enet = 更高的MTU?),而原生代码则获得了“默认”接口(插入一个USB WiFi设备,它就成为您的默认选项 - 很糟糕)。
这对我造成了一些问题,我认为细节并不重要。有两个问题:
1. 是否可以从JNI代码中重新使用Java Socket(可移植?假设使用Sun JDK)。这将完全避免此问题,但迄今为止,我没有看到任何与 java.net.Socket 相关的 JNI/native 代码互操作方法。 2. 鉴于第一个问题的答案可能是否定的,Java是如何创建该套接字(选择接口)的?代码片段受欢迎。我已经在 openjdk 中查找过,但没有找到我要找的内容。
谢谢, Chris
6个回答

10
回答你的第一个问题:从本地代码中重用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,我会看一下。 - Chris Morley
-1 是建议不要在私有 JVM 实现细节上胡闹。可以在不诉诸这种暴行的情况下完成此操作。 - Geoff Reedy
1
你读了问题和我的回答吗?也许你想提供一个不需要访问私有JVM实现细节的解决方案? - Zoran Regvart
"net_util_md.c"的代码库不再可用。 - Sam Ginrich

1

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


1

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

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

请参阅Java 2 SDK版本1.4中引入的JNI增强功能新I/O API了解详细信息。


0

你的第二个问题 -

你总是可以“绑定”到你想要的本地接口(只需要它的IP地址)

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


在Java中调用bind并不是很有帮助——它已经选择了最佳接口。但是获取Java套接字的本地地址,然后在本地端调用bind来使用相同的接口似乎是一个合理的方法。 - Ben Voigt
Ben,那实际上是我所做的,但我有一个报告说本地套接字无法连接到服务器(在将其绑定到与Java套接字相同的本地地址后)。我还没有弄清楚为什么会失败,这就是我想看Java实现的原因。 - Chris Morley

0

我想不出Java为什么会选择比本地代码更好的“本地接口”的原因。它所做的只是调用本地代码,非常类似于您自己的操作。您可能看到的是顺序相关而不是与Java有关。


我能想到一些原因,但我无法确定实际的原因是什么。尽管如此,正如Zoran所指出的那样,行为是不同的。Java代码使用SIO_ROUTING_INTERFACE_QUERY调用WSAIoctl以获取接口。此时,我倾向于彻底摆脱本机套接字代码。 - Chris Morley
没错,一切都归结于伯克利套接字。 - Sam Ginrich

0

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

您的SocketImpl类通过Socket.setSocketImplFactory()和适当的工厂部署。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接