1. Java远程方法调用介绍
在分布式系统中,远程方法调用(Remote Method Invocation,简称RMI)是指一种通过网络从远程计算机程序上请求服务并执行。Java RMI是Java语言提供的远程调用机制,它提供了一种在远程机器上执行Java对象方法的能力,RMI是一种远程对象通信协议。通过使用RMI,Java程序员可以在不需要关心底层的网络协议和I/O手段的细节的情况下进行远程方法调用,就好像本地方法调用一样。RMI是一个协议,Java程序员必须使用Java代码来编写客户端和服务器端程序。
2. RemoteException异常介绍
在RMI中,客户端通过调用远程服务对象上的方法来访问远程服务。远程服务的方法接收到客户端请求后,自然需要将结果通过网络返回给客户端。正常情况下,这一过程是自动的,因为RMI系统会自动将方法调用转换为字节流进行传输,并且自动解码并执行接收到的字节码。但是由于网络传输的不确定性,有时会出现连接被中断的情况,此时RMI系统会抛出一个RemoteException异常。RemoteException异常是Java RMI中非常常见的异常,表示远程方法调用时发生了异常错误。
3. RemoteException的产生原因
3.1 网络不稳定
远程方法调用往往涉及到网络操作,当网络不稳定时,RMI就有可能抛出RemoteException异常。
try {
//获取远程对象
Remote remote = Naming.lookup("rmi://localhost:1099/remoteObj");
//调用远程对象上的方法
String result = ((MyRemoteInterface) remote).hello("world");
} catch (RemoteException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (NotBoundException e) {
e.printStackTrace();
}
在上述代码中,如果在网络不稳定的情况下使用RMI调用远程对象的方法时,就有可能抛出RemoteException异常。
3.2 服务器挂掉
如果服务器端发生故障,远程对象不再可用,客户端调用该对象的方法时就会抛出RemoteException异常。
try {
// 获取远程对象
Remote remote = Naming.lookup("rmi://localhost:1099/remoteObj");
// 调用远程对象上的方法
String result = ((MyRemoteInterface) remote).hello("world");
} catch (RemoteException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (NotBoundException e) {
e.printStackTrace();
}
在上述代码中,如果服务器端发生故障导致远程对象不可用时,客户端调用远程对象上的方法就会抛出RemoteException异常。
4. 解决RemoteException异常的办法
4.1 优化网络连接
为了避免网络不稳定导致的RemoteException异常,可以优化网络连接,减少网络传输的出错率,可以开启RMI远程调用的日志来查看传输过程中的异常信息,具体可以在代码中添加以下代码:
System.setProperty("java.rmi.server.logCalls", "true");
如果觉得LoggingAdapter日志记录器无法满足调试需求,可以使用JConsole进行监控。JConsole是Java自带的一个监控工具,可以启用远程连接来监控运行在Java虚拟机上的应用程序。
4.2 心跳机制
通过在服务器端实现心跳机制,可以判断出服务器是否挂掉,如果挂掉了就重新连接到服务器,可以有效地避免服务器挂掉导致的RemoteException异常。
public void start() throws RemoteException {
// 创建远程对象
MyClass obj = new MyClassImpl();
// 将远程对象和端口号绑定
MyRemoteInterface stub = (MyRemoteInterface) UnicastRemoteObject.exportObject(obj, 0);
LocateRegistry.createRegistry(1099);
// 注册远程对象
Naming.rebind("rmi://localhost:1099/remoteObj", stub);
Timer timer = new Timer();
timer.schedule(new SleepTask(10000), 0, 5000);
System.out.println("MyClass服务启动成功...");
}
class SleepTask extends TimerTask {
private long delay;
SleepTask(long delay) {
this.delay = delay;
}
public void run() {
try {
// 调用远程对象上的方法
String result = impl.sayHello("world");
} catch (RemoteException e) {
// 重新连接到服务器
try {
MyClassImpl impl = new MyClassImpl();
MyRemoteInterface stub = (MyRemoteInterface) UnicastRemoteObject.exportObject(impl, 0);
Naming.unbind("rmi://localhost:1099/remoteObj");
Naming.rebind("rmi://localhost:1099/remoteObj", stub);
} catch (MalformedURLException | RemoteException | NotBoundException ex) {
ex.printStackTrace();
}
}
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上述代码中,通过Timer定时器实现心跳机制,定时每隔5秒发送一次心跳包,如果服务端挂掉了,客户端就会触发异常,并重新连接到服务器。
4.3 尝试重新连接
如果当发生RemoteException异常时,可以尝试多次重新连接,直到连接成功。如果连接成功,就可以执行原本的方法操作。
public class HelloWorldClient {
public static void main(String[] args) throws RemoteException, NotBoundException {
int retry = 3;
for (int i = 0; i < retry; i++) {
try {
String message = "Hello World!";
Registry registry = LocateRegistry.getRegistry("127.0.0.1");
HelloWorld stub = (HelloWorld) registry.lookup("HelloWorld");
String response = stub.sayHello(message);
System.out.println("response: " + response);
break;
} catch (RemoteException e) {
if (i == 2) {
e.printStackTrace();
}
System.out.println("RemoteException:" + message);
} catch (NotBoundException e) {
if (i == 2) {
e.printStackTrace();
}
System.out.println("NotBoundException:" + message);
}
}
}
}
在上面的代码中,为了避免RemoteException异常,客户端会尝试三次重新连接。如果连接成功,就执行指定的方法。如果三次尝试失败,就会打印异常信息。
5. 总结
RemoteException异常是Java远程方法调用中非常常见的异常,表示远程方法调用时发生了异常错误。这种异常可能会因为网络不稳定或是服务器挂掉等原因而产生。为了避免RemoteException异常,我们可以通过优化网络连接、实现心跳机制和尝试重新连接等方式。如果应该灵活选用。