如何在Java中,通过已打开的C语言套接字文件描述符获取Socket或DatagramSocket?

9
我有一个分为两个部分的Linux程序。
第一部分进行NAT遍历,以获取UDP套接字(UDP打洞)或TCP套接字(TCP打洞)。第一部分使用C编写,以便利用本地功能来促进或增强NAT遍历过程。第二部分实际上使用通过第一部分执行的NAT遍历获得的连接套接字。
现在问题来了。我希望第一部分(获取套接字的部分)与第二部分(将套接字用于特定应用程序目的的部分)相互独立。例如,我希望第一部分可重复使用于多种不同的应用程序,这些应用程序都需要在对等方之间建立UDP和TCP连接。
现在,我想让第二部分(应用程序部分)使用Java而不是C或C++编写。我希望第二部分使用由负责NAT遍历的C代码获得的套接字连接。假设第一部分建立了连接并返回了一个结构体:
// Represents a TCP or UDP connection that was obtained in part one.
struct ConnectionObtained {
    int socket_file_descriptor;
    int source_port;
    int destination_port;
    int source_address; // 4 byte ipv4 address
    int destination_address;
    int is_UDP; // 1 for UDP client socket, 0 for TCP client socket 
};

第一部分的C代码可以通过JNI(Java Native Interface)或进程间通信将此POD/struct提供给第二部分中的Java代码。

我希望Java代码使用该信息来构建一个声明类型为java.net.DatagramSocket或java.net.Socket的对象,并在任何期望DatagramSocket或Socket的位置使用该对象。

作为起点,请考虑以下示例代码...

/** 
 * Determines the Unix file descriptor number of the given  {@link ServerSocket}.
 */
private int getUnixFileDescriptor(ServerSocket ss) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
  Field $impl=ss.getClass().getDeclaredField("impl");
  $impl.setAccessible(true);
  SocketImpl socketImpl=(SocketImpl)$impl.get(ss);
  Method $getFileDescriptor=SocketImpl.class.getDeclaredMethod("getFileDescriptor");
  $getFileDescriptor.setAccessible(true);
  FileDescriptor fd=(FileDescriptor)$getFileDescriptor.invoke(socketImpl);
  Field $fd=fd.getClass().getDeclaredField("fd");
  $fd.setAccessible(true);
  return (Integer)$fd.get(fd);
}

代码似乎表明可能可以“在给定的文件描述符上重新创建绑定的{@link ServerSocket}”。这是否意味着也可以在给定的文件描述符上“重新创建绑定的{@link java.net.Socket}”?那么绑定的{@link java.net.DatagramSocket}呢?
/** 
 * Recreates a bound  {@link ServerSocket} on the given file descriptor.
 */
private ServerSocket recreateServerSocket(int fdn) throws Exception {
  FileDescriptor fd=new FileDescriptor();
  Field $fd=FileDescriptor.class.getDeclaredField("fd");
  $fd.setAccessible(true);
  $fd.set(fd,fdn);
  Class $PlainSocketImpl=Class.forName("java.net.PlainSocketImpl");
  Constructor $init=$PlainSocketImpl.getDeclaredConstructor(FileDescriptor.class);
  $init.setAccessible(true);
  SocketImpl socketImpl=(SocketImpl)$init.newInstance(fd);
  ServerSocket ss=new ServerSocket();
  ss.bind(new InetSocketAddress(0));
  Field $impl=ServerSocket.class.getDeclaredField("impl");
  $impl.setAccessible(true);
  $impl.set(ss,socketImpl);
  return ss;
}

如果Java代码只需要关注已连接的套接字,为什么你的Java代码充满了在ServerSocket上的技巧? - user207421
2
如果我理解正确,您是否想要使用已打开的现有套接字作为新Java客户端套接字的基础套接字? - perencia
1
可能是重复问题:http://stackoverflow.com/questions/1243546/can-i-get-a-java-socket-from-a-file-descriptor-number - Andrew Henle
@perencie - 是的。如果我想要将一个已经打开的C套接字作为新Java客户端套接字的底层套接字使用,该怎么办? - Michael Lafayette
@AndrewHernie - 你是否建议子类化java.net.DatagramSocket和java.net.Socket,然后让用户定义的子类在其构造函数中接受FileOutputStream和FileInputStream?它们的发送和接收调用可以转发到流,以便库认为它们正在使用DatagramSocket或Socket。 - Michael Lafayette
显示剩余4条评论
2个回答

4
你提出了两个不同的问题。一个是在不同进程中编写的C代码能否传递绑定的套接字,另一个是在同一进程中编写的C代码是否可以传递绑定的套接字。
对于第一个问题,如果C代码在一个应用程序中,而Java代码在另一个应用程序中,则不可能实现,因为如果这样做,那么多个不同的应用程序将能够传递套接字(没有SCM_RIGHTS)。关闭最初创建和绑定套接字的应用程序会为使用/共享该套接字的其他应用程序创建问题。
至于C代码位于Java应用程序的本地部分(即通过jni),在这种情况下,操作系统无法区分套接字是在用户代码的Java部分还是C部分,因此您不会遇到上一段介绍的问题。可以在Java和本机代码之间传递套接字(文件描述符int和本机套接字描述符)(请参见link),但这并不能告诉您在这种情况下是否实用。

关于如何使用来自jni代码的绑定套接字文件描述符创建java.net.Socket或java.net.DatagramSocket,我不知道。您需要自己尝试。


5
你不需要像回答第三人称那样回答你的问题。这可能会被误解为是分裂式多重人格障碍或类似程序员精神分裂症。这在我第一次看时让我相当困惑,必须承认。 - Imobilis
哈哈哈。我实际上经常自言自语,笑死了。但说真的,我认为如果已经有一个糟糕/不完整/不可用的答案,那么更有可能会有人发表一个(好/完整/可用)的答案。 - Michael Lafayette

0

在 POSIX 系统上,您可以在进程之间传输文件描述符(至少是这样的)。请参考Can I share a file descriptor to another process on linux or are they local to the process?

另外,正如评论中提到的(感谢 @Andrew Henle),您可以通过反射来“黑客” Java,并为现有文件描述符创建 IO 流:Can I get a Java Socket from a file descriptor number?。此外,还可以扩展 Socket 类并覆盖 getInputStream/getOutputStream 方法。

但实际上,我建议您将第一部分用作代理。Java 部分只需在本地主机上打开服务器套接字,然后 C++ 部分会在本地主机和 WAN 地址之间转发流量。


但是实际上我建议您使用第一部分作为代理。那个最后的句子语法不正确。在您将TCP服务器套接字与Java绑定并将本机套接字传递给C ++之后,您将如何将生成的客户端套接字返回到Java? - Michael Lafayette
你不应该传输套接字,而应该传输流量:客户端 <---> C++ 服务器 <---> Java 服务器(<---> - 套接字连接) - sibnick

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