一部分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)或通过进程间通信将第二部分的Java代码提供给第二部分的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; }
解决方法
另外如评论中所提到的(感谢@Andrew Henle),您可以通过反射来破解Java,并为现有文件描述符创建IO流:Can I get a Java Socket from a file descriptor number?也可以扩展Socket类并覆盖getInputStream / getOutputStream方法.
但实际上我建议你使用第一部分作为代理. Java部分只打开localhost上的服务器套接字,然后C部分在本地主机和WAN地址之间重新传输流量.