在Java中,如何从一个已经打开的C套接字的文件描述符中获取一个Socket或DatagramSocket?
In Java, how do you obtain a Socket or DatagramSocket from the file descriptor of an already opened C socket?
我有一个 Linux 程序分为两部分。
一部分进行NAT穿越以获得UDP套接字(UDP打孔)或TCP套接字(TCP打孔)。第一部分是用 C 编写的,以允许促进或增强 NAT 遍历过程的本机功能。第二部分实际使用的是第一部分NAT穿越得到的连接套接字
问题来了。我希望第一部分(获取套接字的部分)独立于第二部分(将套接字用于特定应用目的的部分)。例如,我希望第一部分可重复用于各种不同的应用程序,这些应用程序都需要在对等点之间建立 UDP 和 TCP 连接。
现在,我希望用 Java 而不是 C 或 C++ 编写第二部分(应用程序部分)。我希望第二部分使用由负责 NAT 遍历的 C 代码获得的套接字连接。假设第一部分建立了一个连接,然后 returns 一个结构:
// 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 本机接口)或进程间通信将此 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);
}
该代码似乎可以 "recreates a bound {@link ServerSocket} on the given file descriptor." 这是否意味着也可以 "recreates a bound {@link java.net.Socket} on the given file descriptor"?绑定 {@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;
}
您在问两个不同的问题。您能否传递来自在单独进程中编写的 C 代码的绑定套接字,以及您是否可以传递来自相同进程中编写的 C 代码的绑定套接字。
对于第一部分,不,如果 C 代码在一个应用程序中而 Java 代码在另一个应用程序中是不可能的,因为如果可能的话,多个不同的应用程序将能够传递一个套接字(没有 SCM_RIGHTS)。终止最初创建和绑定套接字的应用程序会给其他应用程序带来问题 using/sharing 该套接字。
至于让 C 代码位于 Java 应用程序的本机部分(即通过 jni),在这种情况下,操作系统将无法区分套接字是否位于 Java部分用户代码或C部分,所以你不要运行进入上一段介绍的问题。可以在 Java 和本机代码(参见 link)之间传递套接字(文件描述符 int 和本机套接字描述符),但这并不能告诉您在这种情况下是否可行。
至于从来自 jni 代码的绑定套接字文件描述符生成 java.net.Socket 或 java.net.DatagramSocket,我不知道。你必须自己尝试一下。
您可以根据 Can I share a file descriptor to another process on linux or are they local to the process?
在进程之间传输文件描述符(至少在 POSIX 系统上)
也如评论中所述(感谢@Andrew Henle),您可以通过反射破解 Java 并为现有文件描述符创建 IO 流:Can I get a Java Socket from a file descriptor number? 也可以扩展套接字 class 并覆盖 getInputStream/getOutputStream 方法。
但实际上我建议你使用第一部分作为代理。 Java 部分只是在本地主机上打开服务器套接字,然后 C++ 部分在本地主机和 WAN 地址之间重新传输流量。
我有一个 Linux 程序分为两部分。
一部分进行NAT穿越以获得UDP套接字(UDP打孔)或TCP套接字(TCP打孔)。第一部分是用 C 编写的,以允许促进或增强 NAT 遍历过程的本机功能。第二部分实际使用的是第一部分NAT穿越得到的连接套接字
问题来了。我希望第一部分(获取套接字的部分)独立于第二部分(将套接字用于特定应用目的的部分)。例如,我希望第一部分可重复用于各种不同的应用程序,这些应用程序都需要在对等点之间建立 UDP 和 TCP 连接。
现在,我希望用 Java 而不是 C 或 C++ 编写第二部分(应用程序部分)。我希望第二部分使用由负责 NAT 遍历的 C 代码获得的套接字连接。假设第一部分建立了一个连接,然后 returns 一个结构:
// 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 本机接口)或进程间通信将此 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);
}
该代码似乎可以 "recreates a bound {@link ServerSocket} on the given file descriptor." 这是否意味着也可以 "recreates a bound {@link java.net.Socket} on the given file descriptor"?绑定 {@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;
}
您在问两个不同的问题。您能否传递来自在单独进程中编写的 C 代码的绑定套接字,以及您是否可以传递来自相同进程中编写的 C 代码的绑定套接字。
对于第一部分,不,如果 C 代码在一个应用程序中而 Java 代码在另一个应用程序中是不可能的,因为如果可能的话,多个不同的应用程序将能够传递一个套接字(没有 SCM_RIGHTS)。终止最初创建和绑定套接字的应用程序会给其他应用程序带来问题 using/sharing 该套接字。
至于让 C 代码位于 Java 应用程序的本机部分(即通过 jni),在这种情况下,操作系统将无法区分套接字是否位于 Java部分用户代码或C部分,所以你不要运行进入上一段介绍的问题。可以在 Java 和本机代码(参见 link)之间传递套接字(文件描述符 int 和本机套接字描述符),但这并不能告诉您在这种情况下是否可行。
至于从来自 jni 代码的绑定套接字文件描述符生成 java.net.Socket 或 java.net.DatagramSocket,我不知道。你必须自己尝试一下。
您可以根据 Can I share a file descriptor to another process on linux or are they local to the process?
在进程之间传输文件描述符(至少在 POSIX 系统上)也如评论中所述(感谢@Andrew Henle),您可以通过反射破解 Java 并为现有文件描述符创建 IO 流:Can I get a Java Socket from a file descriptor number? 也可以扩展套接字 class 并覆盖 getInputStream/getOutputStream 方法。
但实际上我建议你使用第一部分作为代理。 Java 部分只是在本地主机上打开服务器套接字,然后 C++ 部分在本地主机和 WAN 地址之间重新传输流量。