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

时间:2015-09-26 07:28:48

标签: java c sockets tcp udp

我有一个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 /结构提供给第二部分中的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;
}

2 个答案:

答案 0 :(得分:4)

你问的是两个不同的问题。您可以从单独进程中编写的C代码传递绑定套接字,并且可以从同一进程中编写的C代码传递绑定套接字。

对于第一部分,不,如果C代码在一个应用程序中并且Java代码是另一个应用程序是不可能的,因为如果可能的话,那么多个不同的应用程序将能够传递套接字(没有SCM_RIGHTS)。杀死最初创建并绑定套接字的应用程序会给使用/共享该套接字的其他应用程序带来问题。

至于让C代码在Java应用程序的本机部分(即通过jni),在这种情况下,操作系统将无法区分套接字是否在用户代码的Java部分或C部分,所以你不会遇到前一段中介绍的问题。可以在Java和本机代码之间传递套接字(文件描述符int和本机套接字描述符)(请参阅link),但这并不能告诉您它是否在这种情况下是实用的。

至于从jni代码的绑定套接字文件描述符中创建java.net.Socket或java.net.DatagramSocket,我不知道。你必须自己尝试一下。

答案 1 :(得分: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方法。 / p>

但实际上我建议你使用第一部分作为代理。 Java部分只是在localhost上打开服务器套接字,然后C ++部分在localhost和WAN地址之间重新传输流量。