WebSocket Slow - Java and JavaScript

☆樱花仙子☆ 提交于 2020-05-13 14:20:17

问题


I am dealing with encoding of a minecraft plugin. But now I have the following problem, my websocket server respond very very slow.

Here is my WebSocketClass (For the plugin)

// socket server class

package me.mickerd.pcoc;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Collection;

import org.bukkit.Bukkit;
import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;

public class WebsocketServer extends WebSocketServer {
public static WebsocketServer s;

public WebsocketServer(int port) throws UnknownHostException {
    super(new InetSocketAddress(port));
}

public WebsocketServer(InetSocketAddress address) {
    super(address);
}

@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
    WebsocketSessionManager.getSessionManager().openSession(conn.getRemoteSocketAddress().getAddress().getHostAddress());
    Bukkit.getLogger().info(conn.getRemoteSocketAddress().getAddress().getHostName() + " has connected to the Websocket server!");
}

@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
    WebsocketSessionManager.getSessionManager().endSession(conn.getRemoteSocketAddress().getAddress().getHostAddress());
    Bukkit.getLogger().info(conn + " has disconnected form the Websocket server");
}

@Override
public void onMessage(WebSocket conn, String message) {
    Bukkit.getLogger().info("Recieve Websocket packet - " + conn + ":" + message);
    if (message.split(":")[0].equalsIgnoreCase("name")) {
        WebsocketSessionManager.getSessionManager().addSessionUsername(conn.getRemoteSocketAddress().getAddress().getHostAddress(), message.split(":")[1]);
    }
}

public static void runServer() throws InterruptedException, IOException {
    WebSocketImpl.DEBUG = true;
    int port = 8887;
    s = new WebsocketServer(port);
    s.start();
    Bukkit.getLogger().info("Websocket server started on port: " + s.getPort());
}

@Override
public void onError(WebSocket conn, Exception ex) {
    ex.printStackTrace();
    if (conn != null) {
        // some errors like port binding failed may not be assignable to a specific websocket
    }
}

public void sendToAll(String data) {
    Collection<WebSocket> con = connections();
    synchronized (con) {
        for (WebSocket c : con) {
            c.send(data);
        }
    }
}

public void sendData(WebsocketSession session, String data) {
    Collection<WebSocket> con = connections();
    synchronized (con) {
        for (WebSocket c : con) {
            if (c.getRemoteSocketAddress().getAddress().getHostAddress().equalsIgnoreCase(session.getHost())) {
                Bukkit.getLogger().info("Send data packet: " + data);
                c.send(data);
            }
        }
    }
}
}

and this is my receiver in Javascript:

var sound = null;
var name = window.location
document.session.name.value = name

var text = document.session.name.value

var ws = new WebSocket("ws://" + window.location.hostname + ":8887/");

ws.onopen = function () {
        ws.send("name:" + delineate(text));
document.getElementById("title").innerHTML = "Welcome on the music server. Please hold this window open!";

};

ws.onmessage = function (evt) {
function loadScript(url, callback)
{
    // Adding the script tag to the head as suggested before
    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;

    // Then bind the event to the callback function.
    // There are several events for cross browser compatibility.
    script.onreadystatechange = callback;
    script.onload = callback;

    // Fire the loading
    head.appendChild(script);
}
if(evt.data == "stop"){
  sound.fadeOut(0, 3700);
} else {
sound = new Howl({
  urls: ['music/' + evt.data + '.ogg']
}).play();
console.log("playing music");
};
}

ws.onclose = function () {
    alert("Closed!");
};

ws.onerror = function (err) {
    alert("Error: " + err);
};

function delineate(str) {
    theleft = str.indexOf("=") + 1;
    theright = str.lastIndexOf("&");
    return (str.substring(theleft, theright));
}

The reacts very slow, but other things on the server are incredible fast!

Can someone help?


回答1:


The websocket library you are using sends data blocking, this means that a call to c.send( will block until the frame is send.

There are different ways to solve this, for example:

Using a separate async thread for every message:

public void sendToAll(String data) {
    // Ferrybig - added bukkit async task
    Bukkit.getSchedular().runTaskAsynchronously(plugin, new Runnable(){
    @Override public void run(){
        Collection<WebSocket> con = connections();
        synchronized (con) {
            for (WebSocket c : con) {
                c.send(data);
            }
        }
    // Ferrybig - added bukkit async task
    }});
}

While this solves you problem quickly, it isn't a clean solution since a large amount of send messages means that there are a large number of threads created for the purpose of message sending, ie don't send to often, or see next solution:

Using a dedicated thread for the message sending:

Using a dedicated thread for the sending of messages is the better solution, but it comes with its large code.

For this solution, we need to do the following:

  • Use a variable to store the messages that need to be send to every client

    private final BlockingQueue<Pair<WebSocket,String>> messageQueue
                  = new LinkedBlockingDeque<>();
    

    We use a Blocking queue that holds Pair objects containing the web socket and the message to be send. While we also could used the Map.Entry classes from Map, I choose to use a Pair since we can later change the code a little to make it automatically resort message based on priority. The Pair class I used for this answer can be found at What is the equivalent of the C++ Pair in Java?.

  • Using a dedicated thread to send the messages

    We have list of incoming messages now, we now to process the messages as they come in. This can be done by making a task that blocks on messageQueue.take(). The following is a quick implementation of this:

    public class MessageProcessor extends BukkitRunnable {
    
        private BlockingQueue<Pair<WebSocket,String>> messageQueue;
    
        public MessageProcessor (BlockingQueue<Pair<WebSocket,String>> messageQueue) {
            this.messageQueue = messageQueue;
        }
    
        @Override 
        public void run() {
            try {
                Pair<WebSocket,String> next;
                while(true) {
                    next = messageQueue.take();
                    if(next.getFirst() == null) {
                        // Special condition, will be explained later
                        return; // Quit run method
                    }
                    // System.out.println("Send message to " + next.getFirst()); // DEBUG
                    next.getFirst().send(next.getSecond());
                }
            } catch(InterruptedException e) {
                Thread.currentThread().interrupt();
                // Someone wanted to quit our thread, so we are quiting
            } finally {
                messageQueue.clear();
            }
        }
    }  
    

    The above class has 2 special conditions, next.getFirst() == null and catch(InterruptedException e), these will be used when we disable the plugin to quit the task.

    • Start our dedicated task when bukkit is started

    We need to start our task when bukkit and our Websocket server is started, so it can start processing messages and sending data. This is easy to do in our onEnable() with the following code:

    new MessageProcessor (messageQueue).runTaskAsynchronously(this);

    • Stopping the dedicated task

    We need to make sure the dedicated task is stopped when our plugin is disabled to prevent bukkit from spamming the error "This plugin is not properly shutting down its async tasks when it is being reloaded.". This is really easy to do since we have made a special condition for this above.

    To do this, we place the following code in our onDisable():

    messageQueue.add(new Pair<>(null,null));

    • Rewriting our methods to use the messageQueue

    Our last step in the process is to rewrite the sendToAll method to use our queue. This is really easy to do any only requires us to replace 1 line.

    public void sendToAll(String data) {
        Collection<WebSocket> con = connections();
        synchronized (con) {
            for (WebSocket c : con) {
                messageQueue.add(new Pair<>(c,data)); // Ferrybig: Use messageQueue
            }
        }
    }
    

    The same small modification can also be done for sendData method, but isn't done by me as an exercise for the reader.

Sidenotes

A BlockingQueue is designed with concurrent actions in mind, and doesn't require external synchronization.

You may choose to use BlockingQueue.offer() instead of BlockingQueue.add() because the fact the latter throws an exception when the list is full, but the first returns false.

The default size for a LinkedBlockingDeque is Integer.MAX_VALUE and can be changed with its constructor.



来源:https://stackoverflow.com/questions/34700358/websocket-slow-java-and-javascript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!