Java RMI客户端网络连接拒绝故障排除

这从来没有工作过 – 我是第一次设置环境。

下面的多个更新

我的RMI服务器在“守护进程”类型的进程中运行,因为我希望它在系统启动时像服务一样运行(不使用xinitdfunction),所以我可以使用SSL,所以我可以给守护进程命令与通过单独机制进入注册表的内容有关。 (IOW,我没有使用像Runtime.getRuntime().exec("rmiregistry 2020");

我知道RMI服务器已启动并运行,因为它可以正常工作:

 telnet localhost  

我的客户端代码将许多用于SSL的环境变量编码到命令行中以启动它 – 稍后,我将以编程方式进行,但是现在,它开始于:

 $ java -Djavax.net.ssl.trustStore=/var/RMIssl/truststore -Djavax.net.ssl.trustStorePassword= -Djava.rmi.server.hostname=localhost my.package.Test 

起初我得到的是:

 java.rmi.ConnectException: Connection refused to host: localhost; nested excepti on is: java.net.ConnectException: Connection refused 

这几乎没用,除了有错误。 所以,我仔细检查了各种方式,最后用刷新输入了一些跟踪语句,并将其添加到命令行:

 -Djavax.net.debug=all 

这有点难以理解,但仍然很难弄清楚错误的位置,所以我将可疑行放入循环中,捕获exception,现在很清楚它是在注册表中查找特定对象的问题,虽然错误信息确实似乎有误导性。 这是我得到的:

 $ java -Djavax.net.debug=all -Djavax.net.ssl.trustStore=/var/RMIssl/truststore -Djavax.net.ssl.trustStorePassword= -Djava.rmi.server.hostname=localhost my.package.Test Setup the security manager. Find the registry. Lookup testClient. keyStore is : keyStore type is : jks keyStore provider is : init keystore init keymanager of type SunX509 trustStore is: /var/RMIssl/truststore trustStore type is : jks trustStore provider is : init truststore adding as trusted cert: Subject: CN=  Issuer: CN=  Algorithm: RSA; Serial number: 0x Valid from  trigger seeding of SecureRandom done seeding SecureRandom Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_128_CBC_SHA256 java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: java.net.ConnectException: Connection refused enter 'quit' to exit: Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_128_CBC_SHA256 java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: java.net.ConnectException: Connection refused enter 'quit' to exit: 

注意,循环结构允许更好地分析错误; 有关信任库和我的证书的输出不会重复。

这告诉我,它没有找到合适的密码套件来使用,但这都是在同一个系统上! 客户端和服务器都在寻找相同的信任库等。

如果有人认为它有用,这里是客户端代码:

 import java.rmi.*; import java.rmi.registry.*; import java.io.*; import java.net.*; import javax.rmi.ssl.*; public class Test { private static String ServerHost = "localhost"; private static int ServerPort = 6543; private static Registry registry; private static testClient c; public static void main(String[] args) throws Exception { msg("Setup the security manager."); System.setSecurityManager(new RMISecurityManager()); msg("Find the registry."); registry = LocateRegistry.getRegistry(ServerHost, ServerPort, new SslRMIClientSocketFactory()); msg("Lookup testClient."); boolean keepLooping = true; while (keepLooping) { try { c = (testClient)registry.lookup(testClient.class.getSimpleName()); } catch (Exception e) { msg(e.toString()); } keepLooping = !(Prompt("enter 'quit' to exit: ").equals("quit")); } } public static void msg(String message) { System.out.println(message); System.out.flush(); } public static String Prompt(String prompt) { String out = ""; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); try { System.out.print(prompt); out = br.readLine(); } catch (IOException e) { msg("IO error reading from standard-in: "+e.toString()); } return out; } } 

更新一:

在我自己的预感之后,我发现了用于设置SslRMI套接字工厂中使用的密码套件的环境变量。 所以,我把它添加到服务器端:

 javax.rmi.ssl.client.enabledCipherSuites="TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA256" 

这实际上是“不可用”或“不支持”的整个列表 – 可能它太过分了,但我希望服务器能够接受客户端可用的任何内容。

这大大改变了输出! 但是,它仍然挂在同一个声明上,这一次,关闭跟踪,我们得到:

 java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake 

当我添加跟踪时,有很多关于实际选择了哪个协议以及它正在做什么的数据,它最终会得到这个(在此摘录之前大量裁剪):

 ... 0080: 00 0F 00 10 00 11 00 02 00 12 00 04 00 05 00 14 ................ 0090: 00 08 00 16 00 0B 00 02 01 00 .......... main, received EOFException: error main, handling exception: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake main, SEND TLSv1 ALERT: fatal, description = handshake_failure main, WRITE: TLSv1 Alert, length = 2 [Raw write]: length = 7 0000: 15 03 01 00 02 02 28 ......( main, called closeSocket() java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake 

更新二:

根据EJP的建议,我从他对其他人的回答中尊重这个主题,我测试的前提是服务器并没有真正运行注册表,或者至少不是客户端正在寻找的系统/端口组合。

为了测试这个假设,我只需启动和停止服务器,客户端行为就会发生很大变化。 在它没有运行的情况下,我得到的长块交替地是“忽略不可用”或“忽略不支持的”密码套件,后面跟着它们的名字,正如我在上面的第一个案例中报告的那样。

当服务器运行时,客户端给出了一个非常不同的响应,向我发送有关加密连接细节的非常详细的信息,正如我在上面的第一次更新中所报告的那样。 但是,它说远程主机在握手期间关闭了连接,所以当它掉线时显然正在进行握手。 另外,我明白了:

 $ netstat -n -l | grep  tcp 0 0 ::: :::* LISTEN 

我认为这是决定性的……

更新三:

我注意到输出有所改变,所以我应该澄清,等等。当我测试时,我确定我的系统上的端口上有一个监听器来自我的注册表(希望)正确启动,使用相同的信任库和密钥库文件作为客户端,我启动客户端并从SSL获取大量跟踪信息,包括上面提到的密码列表,然后,它列出了一个RandomCookie,空会话ID,另一个密码列表,一些椭圆曲线,并转储一堆字节,这是以下文本提取的地方:

 0080: 00 0F 00 10 00 11 00 02 00 12 00 04 00 05 00 14 ................ 0090: 00 08 00 16 00 0B 00 02 01 00 .......... [Raw read]: length = 5 0000: 15 03 01 00 02 ..... [Raw read]: length = 2 0000: 02 28 .( main, READ: TLSv1 Alert, length = 2 main, RECV TLSv1 ALERT: fatal, handshake_failure main, called closeSocket() main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure 

如果我们收到关于握手被丢弃的干净错误消息,那肯定会很好。 不过,我还没弄明白如何从服务器获得类似的输出 – 这是怎么回事? 我已经提供了javax.net.debug = all?!?!

更新四:

我想知道我在RMI注册表中注册的对象是否存在问题。 我的原始设计是创建两个对象接口。 第一个被实例化并注册。 它的工作是创建实例化但未注册的其他对象; 客户端从注册表中获取第一个对象并在第二个对象中执行该对象,该实例对该客户端是私有的。 然后,客户端调用第二个对象公开的方法,这些方法在服务器端执行,并返回结果。

为了确保这里没有发挥作用,我创建了以下内容。 但请注意,这并没有改变任何东西 – 它只是缩短了很多,因此更适合包括在这里进行全面披露:

 package ; import .*; import java.rmi.*; public interface testobject extends Remote { myAPI.Person getObject() throws RemoteException; } 

然后我使用了界面:

 package ; import .myAPI; import .testobject; import java.rmi.*; import java.rmi.server.*; public class testObject extends UnicastRemoteObject implements testobject { public testObject() throws RemoteException { // Set the security manager to the RMI security manager, to prevent // the client process from using the server in a malicious way. try { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } } catch(Exception e) { System.out.println("SecurityManager load error: \n"+e); } } public myAPI.Person getObject() throws RemoteException { myAPI c = new myAPI(); myAPI.Person TestObject = c.newPerson(); TestObject.FirstName = "Test"; TestObject.LastName = "Object"; return TestObject; } } 

然后,我使用以下行更新了服务器代码:

  try { testObject TestObject = new testObject(); OK = true; } catch (RemoteException e) { msg("Error instantiating TestObject: \n"+e.toString()); } if (OK) { name = testObject.class.getSimpleName(); try { registry.bind(name, ServerProcess); msg("New testObject has been bound to the RMI registry."); } catch(Exception e) { msg("The new testObject could not be bound to the RMI registry: \n"+e.toString()); OK = false; } } 

最后客户:

  testObject to; try { msg("Now trying a 'testObject'..."); to = (testObject)registry.lookup(testObject.class.getSimpleName()); msg("Well! WE DID NOT THROW AN EXCEPTION!"); person = to.getObject(); msg("Name: "+person.FirstName+" "+person.LastName); } catch (Exception e) { msg(e.toString()); } 

不幸的是,它仍然抛出(相同)exception。

还在,“嗯……”