问题
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
andcatch(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