我正在使用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中可行吗?或者,有没有更好的解决方案?
感谢。
答案 0 :(得分:0)
如评论中所述,websockets缺点之一是您将自己指定通信协议。 AFAIK,巨大的开关是最好的选择。为了提高代码的可读性和维护性,我建议使用编码器和解码器。然后,您的问题变成:我应该如何设计我的消息?
你的游戏看起来像拼字游戏。我不知道如何玩拼字游戏,所以让我们以金钱卡片游戏为例。我们假设您有三种类型的操作:
然后您的消息可能看起来像
@OnMessage
正确定义解码器和编码器后,您的开关将更易于阅读并更易于维护。在我的项目中,代码看起来像这样:
case
我更喜欢这种方法,因为:
为了进一步超越代码可读性,维护和效率,如果可以改进代码,可以使用SessionHandler拦截某些CDI事件。我在this answer中举了一个例子。如果您需要更高级的示例,Oracle会提供great tutorial about it。它可能会帮助您改进代码。
答案 1 :(得分:0)
经过一些测试和研究后,我找到了两种避免长时间切换案例的方案。
使用匿名类
匿名类方法是常态,下面的代码显示了如何实现它。我在这个例子中使用了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个不同的任务要执行。这种Anonymous类方法将导致为单个客户端创建100个对象。对于我的应用程序来说,太多的对象创建是不可承受的,其中将有超过5,000个活动并发连接。看看这篇文章http://blogs.microsoft.co.il/gilf/2009/11/22/applying-strategy-pattern-instead-of-using-switch-statements/
使用注释进行反射
我真的很喜欢这种方法。我创建了一个自定义注释来表示方法执行的任务。对象创建没有任何开销,例如在策略模式方法中,因为任务由单个类执行。
注释
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TaskAnnotation {
public String value();
}
下面给出的代码将客户端请求键映射到处理任务的方法。在这里,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
}
最后,我们的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
}
这种方法的主要缺点是性能受到影响。与直接方法调用方法相比,这种方法很慢。此外,许多文章都建议远离反思,除非我们正在处理动态编程。
阅读这些答案,了解有关反思What is reflection and why is it useful?
的更多信息反思表现相关文章
Faster alternatives to Java's reflection
https://dzone.com/articles/the-performance-cost-of-reflection
最终结果
我继续在我的应用程序中使用switch语句,以避免任何性能损失。