2015-09-26 63 views
9

我有一個Linux程序分爲兩部分。在Java中,如何從已打開的C套接字的文件描述符中獲取Socket或DatagramSocket?

一部分通過NAT遍歷來獲得UDP套接字(UDP hole punching)或TCP套接字(TCP hole punching)。第一部分是用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本地接口)或通過提供這種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; 
} 
+0

如果Java代碼只需要關注連接的套接字,爲什麼Java代碼充滿了'ServerSocket?'的技巧? – EJP

+2

如果我理解正確,是否要使用現有的打開套接字作爲底層套接字一個新的Java客戶端套接字? – perencia

+1

可能的重複數據刪除:http://stackoverflow.com/questions/1243546/can-i-get-a-java-socket-from-a-file-descriptor-number –

回答

0

您可以在評論(感謝@Andrew亨勒)提到accroding Can I share a file descriptor to another process on linux or are they local to the process?

而且過程(至少在POSIX系統)之間傳輸的文件描述符可以通過反射破解Java和創建IO流爲現有的文件描述符:Can I get a Java Socket from a file descriptor number?也可以擴展Socket類並重寫getInputStream/getOutputStream方法。

但實際上,我建議你先用一部分代理。 Java部分只是在localhost上打開服務器套接字,然後C++部分在localhost和WAN地址之間重新傳輸通信。

+0

「但實際上我建議你使用第一部分作爲代理」。最後一句話在語法上是不正確的。將TCP服務器套接字與Java綁定並將本地套接字傳遞給C++後,如何將生成的客戶端套接字返回給Java? –

+0

你不應該轉移插座,你應該傳送業務:客戶<---> C++服務器<---> Java服務器(<---> - 套接字連接) – sibnick

3

你在問兩個不同的問題。你可以通過一個單獨的進程寫入的C代碼傳遞一個綁定的套接字,並且你可以通過在同一個進程中寫入的C代碼傳遞一個綁定的套接字。對於第一部分,不,如果C代碼在一個應用程序中,並且Java代碼是另一個代碼是不可能的,因爲如果可能的話,比多個不同的應用程序能夠傳遞一個套接字(沒有SCM_RIGHTS) 。殺死最初創建和綁定套接字的應用程序會爲使用/共享該套接字的其他應用程序創建問題。

至於讓C代碼在Java應用程序的本地部分(即通過jni),在這種情況下,操作系統將無法區分套接字是否位於用戶代碼的Java部分或C部分,因此您不會遇到前一段中介紹的問題。可以在Java和本地代碼之間傳遞一個套接字(文件描述符int和本地套接字描述符)(請參閱link),但是這並不能告訴您這種情況是否可行。

至於從一個來自jni代碼的綁定套接字文件描述符中創建一個java.net.Socket或一個java.net.DatagramSocket,我不知道。你將不得不自己嘗試一下。

+4

你不必像你是第三人一樣回答你的問題。可能被錯誤地解釋爲DID或程序員精神分裂症。我必須承認第一種觀點讓我非常困惑。 – Malina

+0

哈哈哈。我實際上經常跟自己說話,哈哈。但嚴重的是,我認爲如果在那裏已經有一個不好/不完整/非功能的答案,那麼有人會更可能發佈(好的/完整的/功能性的)答案。 –

相關問題