![](/img/trans.png)
[英]How to get and send messages asynchronously from one websocket client to antoher
[英]How to process Websocket messages from client in Java?
我正在使用Websocket用Java开发客户端服务器应用程序。 当前,所有客户端消息都使用切换条件进行处理,如下所示。
@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中可行吗? 还是有更好的解决方案?
谢谢。
如评论中所述,websockets的缺点之一是您将自己指定通信协议。 AFAIK,巨大的开关是最好的选择。 为了提高代码的可读性和维护性,我建议使用编码器和解码器。 然后,您的问题变成:我应该如何设计我的消息?
您的游戏看起来像拼字游戏。 我不知道如何玩Scrabble,所以让我们以带钱的纸牌游戏为例。 假设您有三种类型的动作:
然后您的消息看起来像
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 ...
}
}
我更喜欢这种方法,因为:
@OnMessage
方法可以作为协议摘要来阅读,但详细信息不应在此处显示。 每个case
只能包含几行。 为了超越代码的可读性,维护性和效率,如果可以改善代码,则可以使用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();
}
}
此方法的主要缺点是对象创建。 说,我们有100个不同的任务要执行。 这种匿名类方法将导致为单个客户端创建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();
}
下面给出的代码将客户端请求键映射到处理任务的方法。 在此,地图仅实例化并填充一次。
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
}
这种方法的主要缺点是性能下降。 与直接方法调用方法相比,此方法速度较慢。 此外,除非我们正在处理动态编程,否则许多文章建议不要使用反射。
阅读这些答案,以了解有关反射的更多信息。 什么是反射,为什么有用?
反射性能相关文章
https://dzone.com/articles/the-performance-cost-of-reflection
最后结果
我继续在应用程序中使用switch语句,以避免任何性能下降。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.