Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/371.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在Java中处理来自客户端的Websocket消息?_Java_Websocket_Annotations_Switch Statement_Netty - Fatal编程技术网

如何在Java中处理来自客户端的Websocket消息?

如何在Java中处理来自客户端的Websocket消息?,java,websocket,annotations,switch-statement,netty,Java,Websocket,Annotations,Switch Statement,Netty,我正在使用Websocket用Java开发一个客户机-服务器应用程序。目前,所有客户机消息都使用switch case进行处理,如下所示 @OnMessage public String onMessage(String unscrambledWord, Session session) { switch (unscrambledWord) { case "start": logger.info("Starting the game by sending firs

我正在使用Websocket用Java开发一个客户机-服务器应用程序。目前,所有客户机消息都使用switch case进行处理,如下所示

@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中可能吗?或者,有没有更好的解决方案


谢谢。

如评论中所述,WebSocket的一个缺点是您需要自己指定通信协议。好吧,巨大的开关是最好的选择。为了提高代码的可读性和维护性,我建议使用编码器和解码器。然后,你的问题变成了:我应该如何设计我的消息

你的游戏看起来像拼字游戏。我不知道如何玩拼字游戏,所以让我们以金钱纸牌游戏为例。假设您有三种类型的操作:

  • 全局操作(联接表、离开表…)
  • 金钱行动(下注、分割下注等)
  • 卡牌动作(抽牌等)
  • 然后您的消息可以看起来像

    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{
        // ...
    }
    
    一旦正确定义了解码器和编码器,交换机将更易于读取和维护。在我的项目中,代码如下所示:

    @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 ...
        }
    
    }
    
    我更喜欢这种方法,因为:

  • 消息设计可以利用您的实体设计(如果您在后面使用JPA)
  • 由于消息不再是纯文本,而是对象,因此可以使用枚举,并且枚举在这种切换情况下非常强大。使用相同的逻辑,但在较小的范围内,类抽象也可以很有用
  • ServerEndPoint类仅处理通信。业务逻辑在此类之外处理,可以直接在消息类中处理,也可以在某些EJB中处理。由于这种拆分,代码维护变得更加容易
  • 另外:
    @OnMessage
    方法可以作为协议摘要阅读,但不应在此处显示详细信息。每个
    案例
    只能包含几行
  • 我宁愿避免使用反射:在websocket的特定场景中,反射会破坏代码的可读性

  • 为了进一步提高代码的可读性、维护性和效率,您可以使用SessionHandler拦截一些CDI事件,如果这可以改进代码的话。我举了一个例子。如果您需要更高级的示例,Oracle提供了一个。它可能会帮助您改进代码。

    经过一点测试和研究,我找到了两种方法来避免长切换情况

  • 匿名类方法(策略模式)
  • 带注释的反射
  • 使用匿名类

    匿名类方法是规范,下面的代码演示了如何实现它。在本例中,我使用了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();
        }
    }
    
    下面给出的代码将客户端请求键映射到处理任务的方法。在这里,map只被实例化和填充一次

    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
    }
    
    这种方法的主要缺点是性能受到影响。与直接方法调用方法相比,这种方法速度较慢。此外,许多文章建议远离反射,除非我们处理的是动态规划

    阅读这些答案,了解更多关于反思的信息

    反射性能相关文章

    最终结果


    我继续在应用程序中使用switch语句,以避免性能受到任何影响。

    关于
    @endpoint…
    ,您是说javax.xml.ws.endpoint吗?这看起来更像是web服务,而不是websocket。另外,websocket是JavaEE环境(JSR356)中唯一的标准。另一点,指的是方法可以通过,但IMHO,长开关更容易处理它的websocket只。若我并没有错的话,像Spring这样的框架使用@endpoint注释将api请求定向到特定的方法/类。我想,如果我能得到这个注释的底层实现,我可以构建类似的东西。例如,当客户端发送登录请求时,我可以将请求转发给特定的方法来执行任务,而不使用任何条件语句。这就是我所说的注释。@ServerEndpoint是如何工作的?您在web上有很多很好的教程,例如。如果您想要更准确,您需要更具体一些我已经编辑了我的代码片段。请看一看。我希望你现在更清楚。代码取自您建议的教程。谢谢你的努力。我认为这种方法对我的情况没有帮助。即使我这样做,每个动作都会有switch语句。我正在寻找一些方法来完全消除switch和if语句。我将尝试获取一些有关哈希表的信息,看看是否有任何方法可以避免长切换场景。谢谢。谢谢您的反馈。如果你想办法摆脱这个开关,欢迎在这里分享。它可能会帮助其他用户寻找相同的东西,我已经发布了答案。你的意见是什么?有什么改进或建议吗?我没有考虑
    映射
    ,但我同意你的结论(这是我的第一句话):当你的选择听起来很美观时,它会影响性能,尤其是代码的可读性。此外,你提到的方法听起来非常依赖于命名约定,这很好。从全球范围来看,不仅仅是你的问题,我尽量避免使用反射
    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
    }
    
    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
    }