2014-03-13 82 views
1

我正在通過Java RMI編寫分佈式應用程序。 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服務器impelementation

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); 

      } 
    } 
} 
+0

你需要看的是RMI超時[post](http://stackoverflow.com/questions/1822695/java-rmi-client-timeout) – shazin

+0

客戶端不必'聯繫服務器重新連接'網絡中斷後'。存根仍然有效,服務器只需再試一次。 – EJP

+0

@EJP,你是什麼意思?正如我測試,客戶端必須再次撥打「registry.lookup ...」,否則RemoteException的將被拋出 – xoyoja

回答

-1

你似乎部分地實現客戶端/服務器會話。這是服務器可以跟蹤以確保客戶端有效的令牌。如果服務器與客戶端進行通信時發生錯誤,則應結束會話,並刪除對客戶端的所有引用。

您的服務器已在使用用於unRegisterEventHandler的整數實現會話。你應該跟蹤那些像Map一樣的整數。如果服務器無法連接到客戶端,它應該簡單地取消註冊該客戶端,並通過從地圖中刪除該會話來使會話無效。服務器應刪除對客戶端的所有引用,並且不會嘗試與客戶端進行通信,直到創建新會話。

如果客戶端嘗試與服務器通信,它應該從服務器獲得InvalidException異常。通過這種方式,客戶端可以通過在catch塊中調用RegisterEventHandler來嘗試創建新的會話。

我工作的一個項目,使用ping命令處理了這個問題,就像你在https://code.google.com/p/umuc-team-factor/

與服務器中的所有客戶端通信是在一個環形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); 
    } 
} 
+0

*爲什麼*會因爲暫時的網絡狀況而關閉會話? TCP不這樣做; RMI不這樣做;爲什麼要申請? – EJP

+0

它不一定是。我的程序每隔15秒檢查一次客戶端。這取決於你如何定義臨時。 Apache tomcat允許你爲會話定義一個時間限制。爲什麼不在這個應用程序? –

0

當網絡連接故障(例如,以太網電纜拔出......),RMI服務器和客戶端將不會收到通知,並在嘗試將客戶端的註冊的回調調用RMI服務器出現故障功能。

錯誤,即通知服務器。服務器只需注意這一點,稍後再試。

RMI服務器也不能通知RMI客戶端這個問題。

客戶並不需要知道的。

更糟糕的是,當網絡連接恢復時,RMI客戶端服務仍然會失去與RMI服務器的聯繫,因爲沒有人通知她重新連接。

客戶端不必「重新連接」。 RMI中沒有連接或重新連接步驟。只要客戶端的JVM和遠程對象分別保持並導出,服務器上的存根仍然有效,並且可以繼續被服務器使用。

你解決一個非問題。

+0

我明白你來自哪裏。只要JVM啓動並且遠程對象被導出,遠程對象仍然有效。我看到的問題是,如果JVM關閉或遠程對象未導出,服務器將獲得與臨時網絡中斷相同的異常。我不相信你可以區分這兩個問題,所以他們必須得到相同的對待。如果這被忽略,那麼服務器將最終跟蹤永遠不會工作的遠程對象,這是內存泄漏。在這麼多失敗的嘗試之後,Ping客戶端並刪除它們似乎是唯一的選擇。 –

+0

如果對方去向下和向上,你會得到NoSuchObjectException而不是連接異常,表示一個陳舊的存根,這肯定必須重新獲取。如果一個存根一直不工作,當然它必須丟棄並重新獲得。但是,如果情況是暫時的,你應該允許自己進行幾次重試。我什麼也沒有說'忽略'任何事情。 – EJP

+0

我做了一個關於忽略這個問題的一般性陳述。我相信你在上面的某個地方回答了這個問題:「你的解決方案沒有問題。」 –