2017-06-05 71 views
1

我正在使用Websocket在Java中開發客戶端 - 服務器應用程序。目前,所有客戶端消息都使用switch-case進行處理,如下所示。如何在Java中處理來自客戶端的Websocket消息?

@OnMessage 
public String onMessage(String unscrambledWord, Session session) { 
    switch (unscrambledWord) { 
    case "start": 
     logger.info("Starting the game by sending first word"); 
     String scrambledWord = WordRepository.getInstance().getRandomWord().getScrambledWord(); 
     session.getUserProperties().put("scrambledWord", scrambledWord); 
     return scrambledWord; 
    case "quit": 
     logger.info("Quitting the game"); 
     try { 
      session.close(new CloseReason(CloseCodes.NORMAL_CLOSURE, "Game finished")); 
     } catch (IOException e) { 
      throw new RuntimeException(e); 
     } 
    } 
    String scrambledWord = (String) session.getUserProperties().get("scrambledWord"); 
    return checkLastWordAndSendANewWord(scrambledWord, unscrambledWord, session); 
} 

服務器必須處理超過50個來自客戶端的不同請求,並導致超過50個case語句。未來,我預計它會增長。有沒有更好的方法來處理來自客戶端的WebSocket消息?或者,這是如何通常完成的?

我讀了一些關於使用哈希表來避免長時間切換的情況,通過映射到函數指針。這在Java中可能嗎?或者,有沒有更好的解決方案?

謝謝。

+0

關於'@endpoint ...',你在談論javax.xml.ws.Endpoint嗎?這看起來很web服務,而不是websocket。 AFAIK,websocket是Java EE環境(JSR 356)中的唯一標準。其他方面,指的是可以通過[Reflection](https://stackoverflow.com/a/161005/4906586)完成方法,但恕我直言,長開關更容易處理 – Al1

+0

它的websocket只。如果我沒有錯,像Spring這樣的框架使用@endpoint註釋來將API請求定向到特定的方法/類。我想,如果我能得到這個註解的底層實現,我可以建立類似的東西。例如,當客戶端發送請求登錄時,我可以將請求轉發到特定的方法來執行任務,而不使用任何條件語句。 –

+0

這是我正在談論的註釋。 @ServerEndpoint是如何工作的?http://docs.oracle.com/javaee/7/api/javax/websocket/server/ServerEndpoint.html –

回答

0

經過一番測試和研究後,我發現了兩種方法來避免長時間切換的情況。

  1. 匿名類方法(策略模式)
  2. 思考與註解

使用匿名類

匿名類方法是常態和下面的代碼顯示如何實現它。在這個例子中我使用了Runnable。如果需要更多控制,請創建一個自定義界面。

public class ClientMessageHandler { 

    private final HashMap<String, Runnable> taskList = new HashMap<>(); 

    ClientMessageHandler() { 

     this.populateTaskList(); 
    } 

    private void populateTaskList() { 

     // Populate the map with client request as key 
     // and the task performing objects as value 

     taskList.put("action1", new Runnable() { 
      @Override 
      public void run() { 
       // define the action to perform. 
      } 
     }); 

     //Populate map with all the tasks 
    } 

    public void onMessageReceived(JSONObject clientRequest) throws JSONException { 

     Runnable taskToExecute = taskList.get(clientRequest.getString("task")); 

     if (taskToExecute == null) 
      return; 

     taskToExecute.run(); 
    } 
} 

該方法的主要缺點是創建對象。比方說,我們有100個不同的任務要執行。這種匿名類方法將導致爲單個客戶端創建100個對象。太多的對象創建對於我的應用程序來說是不可承受的,其中將有超過5,000個活動併發連接。看看這篇文章http://blogs.microsoft.co.il/gilf/2009/11/22/applying-strategy-pattern-instead-of-using-switch-statements/

反思與註釋

我真的很喜歡這種方法。我創建了一個自定義註釋來表示由方法執行的任務。對象創建沒有任何開銷,就像在Strategy模式方法中一樣,因爲任務是由一個類執行的。

註釋

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 

public @interface TaskAnnotation { 
    public String value(); 
} 

下面給出的代碼映射客戶端請求鍵,其處理任務的方法。在這裏,地圖被實例化並且僅填充一次。

public static final HashMap<String, Method> taskList = new HashMap<>(); 

public static void main(String[] args) throws Exception { 

    // Retrieves declared methods from ClientMessageHandler class 

    Method[] classMethods = ClientMessageHandler.class.getDeclaredMethods(); 

    for (Method method : classMethods) {    
     // We will iterate through the declared methods and look for 
     // the methods annotated with our TaskAnnotation 

     TaskAnnotation annot = method.getAnnotation(TaskAnnotation.class); 

     if (annot != null) {     
      // if a method with TaskAnnotation is found, its annotation 
      // value is mapped to that method. 

      taskList.put(annot.value(), method); 
     } 
    } 

    // Start server 
} 

如今終於,我們ClientMessageHandler類看起來像這種方式的以下

public class ClientMessageHandler { 

    public void onMessageReceived(JSONObject clientRequest) throws JSONException { 

     // Retrieve the Method corresponding to the task from map 
     Method method = taskList.get(clientRequest.getString("task")); 

     if (method == null) 
      return; 

     try { 
      // Invoke the Method for this object, if Method corresponding 
      // to client request is found 

      method.invoke(this); 
     } catch (IllegalAccessException | IllegalArgumentException 
       | InvocationTargetException e) { 
      logger.error(e); 
     } 
    } 

    @TaskAnnotation("task1") 
    public void processTaskOne() { 

    } 

    @TaskAnnotation("task2") 
    public void processTaskTwo() { 

    } 

    // Methods for different tasks, annotated with the corresponding 
    // clientRequest code 
} 

主要缺點是對性能的影響。與直接方法調用方法相比,此方法速度較慢。此外,許多文章都建議遠離Reflection,除非我們正在處理動態編程。

閱讀這些答案更多地瞭解反射What is reflection and why is it useful?

反射性能相關的文章

Faster alternatives to Java's reflection

https://dzone.com/articles/the-performance-cost-of-reflection

最終結果

我繼續在我的應用程序中使用switch語句以避免任何性能問題。

0

正如評論中提到的那樣,websocket的一個缺點是您要自己指定通信協議。 AFAIK,巨大的開關是最好的選擇。爲了提高代碼的可讀性和維護性,我建議使用編碼器和解碼器。然後,你的問題就變成了:我應該如何設計我的信息?

你的遊戲看起來像拼字遊戲。我不知道如何玩拼字遊戲,所以讓我們以錢打卡片遊戲的例子。讓我們假設你有三種類型的動作:

  1. 全球行動(連接表,離開表...)
  2. 貨幣措施(下賭注,賭裂,...)
  3. 卡操作(畫卡等)

那麼你的消息可能看起來像

public class AbstractAction{ 
    // not relevant for global action but let's put that aside for the example 
    public abstract void endTurn(); 
} 

public class GlobalAction{ 
    // ... 
} 

public class MoneyAction{ 

    enum Action{ 
     PLACE_BET, PLACE_MAX_BET, SPLIT_BET, ...; 
    } 

    private MoneyAction.Action action; 
    // ... 
} 

public class CardAction{ 
    // ... 
} 

一旦你的解碼器和編碼器正確定義,您SWI tch會更容易閱讀和更容易維護。在我的項目,代碼應該是這樣的:

@ServerEndPoint(value = ..., encoders = {...}, decoders = {...}) 
public class ServerEndPoint{ 

    @OnOpen 
    public void onOpen(Session session){ 
     // ... 
    } 

    @OnClose 
    public void onClose(Session session){ 
     // ... 
    } 

    @OnMessage 
    public void onMessage(Session session, AbstractAction action){ 

     // I'm checking the class here but you 
     // can use different check such as a 
     // specific attribute 

     if(action instanceof GlobalAction){ 
      // do some stuff 
     } 

     else if (action instanceof CardAction){ 
      // do some stuff 
     } 

     else if (action instance of MoneyAction){ 
      MoneyAction moneyAction = (MoneyAction) action; 
      switch(moneyAction.getAction()){ 
       case PLACE_BET: 
        double betValue = moneyAction.getValue(); 
        // do some stuff here 
        break; 
       case SPLIT_BET: 
        doSomeVeryComplexStuff(moneyAction); 
        break; 
      } 
     } 

    } 


    private void doSomeVeryComplexStuff(MoneyAction moneyAction){ 
     // ... do something very complex ... 
    } 

} 

我更喜歡這種做法,因爲:

  1. 這些消息的設計可以充分利用你的實體設計(如果你使用JPA後面)
  2. 作爲消息不再是純文本,但可以使用對象,枚舉,枚舉在這種開關情況下非常強大。使用相同的邏輯,但較少擴展,類抽象也可以有用
  3. ServerEndPoint類只處理通信。業務邏輯是從這個類中處理出來的,可以直接在Messages類或某些EJB中處理。由於這種拆分,代碼維護要容易得多
  4. 紅利:@OnMessage方法可以作爲協議摘要讀取,但不應在此處顯示細節。每個case只能包含幾行。
  5. 我寧願避免使用反思:它會毀了你的代碼的可讀性,在WebSocket的具體情況

爲了進一步超越代碼的可讀性,維護和效率,可以使用SessionHandler攔截一些CDI事件如果這可以改善你的代碼。我在this answer舉了一個例子。如果您需要更高級的示例,則Oracle提供great tutorial about it。它可以幫助你改進你的代碼。

+0

我不認爲這種方法將有助於我的情況。即使我這樣做,每個動作都會有switch語句。我正在尋找一些方法來徹底消除開關和if語句。我將嘗試獲取有關散列表的一些信息,並查看是否有任何方法可以避免長時間切換場景。謝謝。 –

+0

感謝您的反饋。如果您找到擺脫開關的方法,請隨時在此分享。它可以幫助其他用戶尋找相同的東西 – Al1

+0

我已經發布了一個答案。你有什麼意見?任何改進或建議? –

相關問題