回调中的 RMI 连接失败检测
Posted
技术标签:
【中文标题】回调中的 RMI 连接失败检测【英文标题】:RMI connection failure detection on callback 【发布时间】:2014-03-13 03:47:57 【问题描述】:我正在通过 Java RMI 编写一个分布式应用程序。 RMI客户端向RMI服务器注册事件处理程序/回调,服务器在需要时调用客户端的回调函数。现在的问题是,当网络连接失败时(例如,网线拔掉了……),RMI服务器和客户端不会得到通知,并且当尝试调用客户端注册的回调函数时,RMI服务器会失败。RMI服务器也无法将此问题通知 RMI 客户端。更糟糕的是,当网络连接恢复时,RMI 客户端服务仍然会失去与 RMI 服务器的联系,因为没有人通知她重新连接。
我目前的想法是在单独的线程中在 RMI 客户端中实现一个 ping() 方法。 该线程可以定期唤醒并检查服务器。 如果失败,则闹剧重新连接。
还有其他优雅的解决方案吗?希望大家帮忙!
界面
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MyInterface extends Remote
public int RegisterEventHandler(RemoteMyEventHandler eventHandler) throws RemoteException;
public void unRegisterEventHandler(int eventHandlerId) throws RemoteException;
RMI 服务器实现
import java.rmi.RemoteException;
import com.me.MyInterface;
public class MyInterfaceImpl implements MyInterface
public void init()
try
//... initialize RMI server....
//....
catch (Exception ex)
ex.printStackTrace();
@Override
public int RegisterEventHandler(RemoteMyEventHandler eventHandler)
throws RemoteException
return MyEventHandlerImp.getInstance().addHandler(eventHandler);
@Override
public void unRegisterEventHandler(int eventHandlerId)
throws RemoteException
MyEventHandlerImp.getInstance().removeHandler(eventHandlerId);
//handler.notifyEventSnap(events);
RMI 客户端实现
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Properties;
import org.apache.log4j.Logger;
import com.me.MyInterface;
public class MyService implements NotifyHandler
private MyInterface client;
private MyEventHandler myEventHandler;
private void connectToServer()
try
//...
Registry registry = LocateRegistry.getRegistry(rmiHost, rmiPort);
client = (MyInterface) registry.lookup(MyCInterface.class.getName());
catch (RemoteException er)
catch (NotBoundException en)
catch (Exception en)
private void startService()
//Attach my event handler
if(client != null)
myEventHandler = new MyEventHandler();
myEventHandlerId = client.RegisterEventHandler(myEventHandler);
【问题讨论】:
你需要看的是RMI Timeout post 网络中断后,客户端不必“联系服务器重新连接”。存根仍然有效,服务器只需要再试一次。 @EJP,你是什么意思?正如我测试的那样,客户端必须再次调用'registry.lookup...',否则会抛出RemoteException 除非服务器 JVM 重新启动或服务器取消导出其远程对象,否则不会。请提供他的堆栈跟踪。将其编辑到您的问题中。 【参考方案1】:当网络连接失败时(例如网线被拔掉...),RMI服务器和客户端不会收到通知,并且RMI服务器在尝试调用客户端注册的回调函数时失败。
Err,那个是给服务器的通知。服务器只需要注意这一点,然后再试一次。
RMI 服务器也无法将此问题通知给 RMI 客户端。
客户不需要知道。
更糟糕的是,当网络连接恢复时,RMI 客户端服务仍然会失去与 RMI 服务器的联系,因为没有人通知她重新连接。
客户端不必“重新连接”。 RMI 中没有连接或重新连接步骤。只要客户端的 JVM 和远程对象分别保持启动和导出,服务器端的 stub 就保持有效,可以继续被服务器使用。
你正在解决一个非问题。
【讨论】:
我知道你来自哪里。只要 JVM 启动并导出远程对象,远程对象仍然有效。我看到的问题是,如果 JVM 出现故障或远程对象未导出,服务器将获得与临时网络中断相同的异常。我不相信你可以区分这两个问题,所以它们必须被同等对待。如果这被忽略,那么服务器将最终跟踪永远不会工作的远程对象,这是内存泄漏。在多次尝试失败后 Ping 客户端并删除它们似乎是唯一的选择。 如果对等体出现故障和上升,您将得到 NoSuchObjectException 而不是连接异常,这表明必须重新获取陈旧的存根。如果存根始终无法正常工作,那么它当然必须丢弃并重新获取。但是你应该允许自己重试几次,以防情况是暂时的。而且我没有说“忽略”任何事情。 我做了一个关于忽略这个问题的一般性声明。我确定您在“您正在解决一个非问题”上方的某个地方回答了这个问题。【参考方案2】:您似乎正在部分实现客户端/服务器会话。这是服务器可以跟踪以确保客户端有效的令牌。如果服务器与客户端通信时出现错误,则应结束会话并删除对客户端的所有引用。
您的服务器已经使用用于 unRegisterEventHandler 的整数实现会话。您应该在地图之类的地方跟踪这些整数。如果服务器无法连接到客户端,它应该简单地取消注册该客户端并通过从映射中删除它来使会话无效。服务器应删除对客户端的所有引用,并且在创建新会话之前不要尝试与客户端通信。
如果客户端尝试与服务器通信,它应该从服务器获得 InvalidException 异常。这样,客户端可以通过在 catch 块中调用 RegisterEventHandler 来尝试建立新会话。
我参与了一个项目,该项目使用您在 https://code.google.com/p/umuc-team-factor/ 建议的 ping 来解决此问题
所有客户端与服务器的通信都在一个循环的 try catch 块中,例如
private void getSession()
while(isRun())
try
if(server == null)
Logger.getLogger(JobClient.class.getName()).info("Server is null.");
setupServer();
UUID sid = server.getSession(this);
synchronized (this)
id = sid;
Logger.getLogger(JobClient.class.getName()).info("Session id is " + id);
return;
catch (RemoteException ex)
Logger.getLogger(JobClient.class.getName()).info("Could not get session from server: " + ex + ". setting up server.");
setupServer();
这会尝试建立与服务器的会话,直到程序停止。
如果抛出 RemoteException,所有服务器与客户端的通信都应该结束客户端的会话。 c.status() 类似于 ping。
List<UUID> endSessions = new ArrayList<UUID>();
for (UUID id : copy.keySet())
ClientCallback c = copy.get(id).client;
try
ClientStatus status = c.status();
Logger.getLogger(ProcessManager.class.getName()).info("got client status for " + id + ": " + status.getSessionID() + " -" + status.getJobStatus());
if (status.getSessionID() == null || !status.getSessionID().equals(id))
endSessions.add(id);
catch (Exception ex)
endSessions.add(id);
Logger.getLogger(ProcessManager.class.getName()).log(Level.SEVERE, null, ex);
for (UUID id : endSessions)
try
endSession(id);
catch (SessionExpiredException ex)
Logger.getLogger(ProcessManager.class.getName()).log(Level.SEVERE, null, ex);
【讨论】:
为什么仅仅因为临时的网络状况而关闭会话? TCP 不这样做。 RMI 不这样做。为什么要申请? 不必如此。我的程序每 15 秒检查一次客户端。这取决于你如何定义临时的。 Apache tomcat 允许您定义会话的时间限制。为什么不在这个应用程序中?以上是关于回调中的 RMI 连接失败检测的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法在出现警告消息框之前检测是不是使用 VBA 建立数据连接失败?