mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
Committing stream efficiency fixes from old SVN branch.
This commit is contained in:
@@ -19,6 +19,7 @@ package net.sourceforge.guacamole.net;
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import javax.servlet.http.HttpSessionBindingEvent;
|
import javax.servlet.http.HttpSessionBindingEvent;
|
||||||
@@ -33,6 +34,7 @@ public class GuacamoleSession {
|
|||||||
private GuacamoleConfiguration config;
|
private GuacamoleConfiguration config;
|
||||||
private final HttpSession session;
|
private final HttpSession session;
|
||||||
private Client client;
|
private Client client;
|
||||||
|
private ReentrantLock instructionStreamLock;
|
||||||
|
|
||||||
private class SessionVNCClient extends VNCClient implements HttpSessionBindingListener {
|
private class SessionVNCClient extends VNCClient implements HttpSessionBindingListener {
|
||||||
|
|
||||||
@@ -68,6 +70,7 @@ public class GuacamoleSession {
|
|||||||
config = new GuacamoleConfiguration(context);
|
config = new GuacamoleConfiguration(context);
|
||||||
|
|
||||||
client = (Client) session.getAttribute("CLIENT");
|
client = (Client) session.getAttribute("CLIENT");
|
||||||
|
instructionStreamLock = (ReentrantLock) session.getAttribute("INSTRUCTION_STREAM_LOCK");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +101,9 @@ public class GuacamoleSession {
|
|||||||
|
|
||||||
session.setAttribute("CLIENT", client);
|
session.setAttribute("CLIENT", client);
|
||||||
|
|
||||||
|
instructionStreamLock = new ReentrantLock();
|
||||||
|
session.setAttribute("INSTRUCTION_STREAM_LOCK", instructionStreamLock);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,4 +126,8 @@ public class GuacamoleSession {
|
|||||||
client.disconnect();
|
client.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReentrantLock getInstructionStreamLock() {
|
||||||
|
return instructionStreamLock;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ import java.io.IOException;
|
|||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.zip.DeflaterOutputStream;
|
import java.util.zip.DeflaterOutputStream;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import net.sourceforge.guacamole.Client;
|
import net.sourceforge.guacamole.Client;
|
||||||
@@ -39,38 +40,13 @@ public class InstructionStream extends GuacamoleServlet {
|
|||||||
@Override
|
@Override
|
||||||
protected void handleRequest(GuacamoleSession session, HttpServletRequest request, HttpServletResponse response) throws GuacamoleException {
|
protected void handleRequest(GuacamoleSession session, HttpServletRequest request, HttpServletResponse response) throws GuacamoleException {
|
||||||
|
|
||||||
// Instruction buffer
|
ReentrantLock instructionStreamLock = session.getInstructionStreamLock();
|
||||||
StringBuilder instructions = new StringBuilder();
|
instructionStreamLock.lock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Query new update from VNC server
|
response.setContentType("text/plain");
|
||||||
Client client = session.getClient();
|
OutputStream out = response.getOutputStream();
|
||||||
|
|
||||||
int messageLimit = Integer.parseInt(request.getParameter("messageLimit"));
|
|
||||||
|
|
||||||
// For all messages, up to given message limit.
|
|
||||||
Instruction message = client.nextInstruction(true); // Block until first message is read
|
|
||||||
while (message != null) {
|
|
||||||
|
|
||||||
// Add message
|
|
||||||
instructions.append(message.toString());
|
|
||||||
|
|
||||||
// No more messages if we're exceeding our limit
|
|
||||||
if (instructions.length() >= messageLimit) break;
|
|
||||||
|
|
||||||
message = client.nextInstruction(false); // Read remaining messages, do not block.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
instructions.append(new ErrorInstruction(e.getMessage()).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Get output bytes
|
|
||||||
byte[] outputBytes = instructions.toString().getBytes("UTF-8");
|
|
||||||
|
|
||||||
// Compress if enabled and supported by browser
|
// Compress if enabled and supported by browser
|
||||||
if (session.getConfiguration().getCompressStream()) {
|
if (session.getConfiguration().getCompressStream()) {
|
||||||
@@ -84,22 +60,14 @@ public class InstructionStream extends GuacamoleServlet {
|
|||||||
// Use gzip if supported
|
// Use gzip if supported
|
||||||
if (encoding.equals("gzip")) {
|
if (encoding.equals("gzip")) {
|
||||||
response.setHeader("Content-Encoding", "gzip");
|
response.setHeader("Content-Encoding", "gzip");
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
out = new GZIPOutputStream(out);
|
||||||
OutputStream zout = new GZIPOutputStream(bos);
|
|
||||||
zout.write(outputBytes);
|
|
||||||
zout.close();
|
|
||||||
outputBytes = bos.toByteArray();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use deflate if supported
|
// Use deflate if supported
|
||||||
if (encoding.equals("deflate")) {
|
if (encoding.equals("deflate")) {
|
||||||
response.setHeader("Content-Encoding", "deflate");
|
response.setHeader("Content-Encoding", "deflate");
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
out = new DeflaterOutputStream(out);
|
||||||
OutputStream zout = new DeflaterOutputStream(bos);
|
|
||||||
zout.write(outputBytes);
|
|
||||||
zout.close();
|
|
||||||
outputBytes = bos.toByteArray();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,13 +77,42 @@ public class InstructionStream extends GuacamoleServlet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response.setContentType("text/plain");
|
try {
|
||||||
response.setContentLength(outputBytes.length);
|
|
||||||
|
|
||||||
// Use default output stream if no compression.
|
// Query new update from VNC server
|
||||||
OutputStream out = response.getOutputStream();
|
Client client = session.getClient();
|
||||||
out.write(outputBytes);
|
|
||||||
|
// For all messages, until another stream is ready (we send at least one message)
|
||||||
|
Instruction message = client.nextInstruction(true); // Block until first message is read
|
||||||
|
while (message != null) {
|
||||||
|
|
||||||
|
// Get message output bytes
|
||||||
|
byte[] outputBytes = message.toString().getBytes("UTF-8");
|
||||||
|
out.write(outputBytes);
|
||||||
|
out.flush();
|
||||||
|
response.flushBuffer();
|
||||||
|
|
||||||
|
// No more messages another stream can take over
|
||||||
|
if (instructionStreamLock.hasQueuedThreads())
|
||||||
|
break;
|
||||||
|
|
||||||
|
message = client.nextInstruction(false); // Read remaining messages, do not block.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
Instruction message = new ErrorInstruction(e.getMessage());
|
||||||
|
byte[] outputBytes = message.toString().getBytes("UTF-8");
|
||||||
|
out.write(outputBytes);
|
||||||
|
out.flush();
|
||||||
|
response.flushBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// End-of-instructions marker
|
||||||
|
out.write(';');
|
||||||
out.flush();
|
out.flush();
|
||||||
|
response.flushBuffer();
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (UnsupportedEncodingException e) {
|
catch (UnsupportedEncodingException e) {
|
||||||
throw new GuacamoleException("UTF-8 not supported by Java.", e);
|
throw new GuacamoleException("UTF-8 not supported by Java.", e);
|
||||||
@@ -123,6 +120,9 @@ public class InstructionStream extends GuacamoleServlet {
|
|||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
throw new GuacamoleException("I/O error writing to servlet output stream.", e);
|
throw new GuacamoleException("I/O error writing to servlet output stream.", e);
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
instructionStreamLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1036,20 +1036,22 @@ public class VNCClient extends Client {
|
|||||||
synchronized (instructionLock) {
|
synchronized (instructionLock) {
|
||||||
if (instructions.size() == 0) {
|
if (instructions.size() == 0) {
|
||||||
|
|
||||||
try {
|
if (blocking) {
|
||||||
// Send framebuffer update request
|
try {
|
||||||
synchronized (output) {
|
// Send framebuffer update request
|
||||||
output.writeByte(MESSAGE_FRAMEBUFFER_UPDATE_REQUEST);
|
synchronized (output) {
|
||||||
output.writeBoolean(!needRefresh); // Incremental
|
output.writeByte(MESSAGE_FRAMEBUFFER_UPDATE_REQUEST);
|
||||||
output.writeShort(0); // x
|
output.writeBoolean(!needRefresh); // Incremental
|
||||||
output.writeShort(0); // y
|
output.writeShort(0); // x
|
||||||
output.writeShort(frameBufferWidth); // width
|
output.writeShort(0); // y
|
||||||
output.writeShort(frameBufferHeight); // height
|
output.writeShort(frameBufferWidth); // width
|
||||||
output.flush();
|
output.writeShort(frameBufferHeight); // height
|
||||||
|
output.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new GuacamoleException("Could not send framebuffer update request to VNC server (network error).", e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
throw new GuacamoleException("Could not send framebuffer update request to VNC server (network error).", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle incoming messages, blocking until one message exists
|
// Handle incoming messages, blocking until one message exists
|
||||||
|
@@ -202,21 +202,12 @@ function VNCClient(display) {
|
|||||||
mouse_xmlhttprequest.open("GET", "pointer?" + mouseEventBuffer);
|
mouse_xmlhttprequest.open("GET", "pointer?" + mouseEventBuffer);
|
||||||
mouseEventBuffer = ""; // Clear buffer
|
mouseEventBuffer = ""; // Clear buffer
|
||||||
|
|
||||||
var eventSendStart = new Date().getTime();
|
|
||||||
|
|
||||||
// Once response received, send next queued event.
|
// Once response received, send next queued event.
|
||||||
mouse_xmlhttprequest.onreadystatechange = function() {
|
mouse_xmlhttprequest.onreadystatechange = function() {
|
||||||
|
|
||||||
// Update round-trip-time figures
|
if (mouse_xmlhttprequest.readyState == 4)
|
||||||
if (mouse_xmlhttprequest.readyState == 2) {
|
|
||||||
var eventSendEnd = new Date().getTime();
|
|
||||||
totalRoundTrip += eventSendEnd - eventSendStart;
|
|
||||||
roundTripSamples++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouse_xmlhttprequest.readyState == 4) {
|
|
||||||
sendPendingMouseEvents();
|
sendPendingMouseEvents();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mouse_xmlhttprequest.send(null);
|
mouse_xmlhttprequest.send(null);
|
||||||
@@ -239,21 +230,6 @@ function VNCClient(display) {
|
|||||||
|
|
||||||
var clipboard_xmlhttprequest = new XMLHttpRequest();
|
var clipboard_xmlhttprequest = new XMLHttpRequest();
|
||||||
clipboard_xmlhttprequest.open("POST", "clipboard");
|
clipboard_xmlhttprequest.open("POST", "clipboard");
|
||||||
|
|
||||||
var sendStart = new Date().getTime();
|
|
||||||
|
|
||||||
// Update round trip metrics
|
|
||||||
clipboard_xmlhttprequest.onreadystatechange = function() {
|
|
||||||
|
|
||||||
// Update round-trip-time figures
|
|
||||||
if (clipboard_xmlhttprequest.readyState == 2) {
|
|
||||||
var sendEnd = new Date().getTime();
|
|
||||||
totalRoundTrip += sendEnd - sendStart;
|
|
||||||
roundTripSamples++;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
clipboard_xmlhttprequest.send(data);
|
clipboard_xmlhttprequest.send(data);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -310,12 +286,6 @@ function VNCClient(display) {
|
|||||||
showError(errors[errorIndex].getMessage());
|
showError(errors[errorIndex].getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data transfer statistics
|
|
||||||
var totalTransferred = 0;
|
|
||||||
var totalTime = 0;
|
|
||||||
var totalRoundTrip = 0;
|
|
||||||
var roundTripSamples = 0;
|
|
||||||
|
|
||||||
var clipboardHandler = null;
|
var clipboardHandler = null;
|
||||||
|
|
||||||
this.setClipboardHandler = function(handler) {
|
this.setClipboardHandler = function(handler) {
|
||||||
@@ -325,7 +295,6 @@ function VNCClient(display) {
|
|||||||
|
|
||||||
function handleResponse(xmlhttprequest) {
|
function handleResponse(xmlhttprequest) {
|
||||||
|
|
||||||
var start = null; // Start of download time
|
|
||||||
var startOffset = null;
|
var startOffset = null;
|
||||||
|
|
||||||
var instructionStart = 0;
|
var instructionStart = 0;
|
||||||
@@ -345,7 +314,6 @@ function VNCClient(display) {
|
|||||||
if (nextRequest == null)
|
if (nextRequest == null)
|
||||||
nextRequest = makeRequest();
|
nextRequest = makeRequest();
|
||||||
|
|
||||||
start = new Date().getTime();
|
|
||||||
startOffset = 0;
|
startOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,11 +334,6 @@ function VNCClient(display) {
|
|||||||
var current = xmlhttprequest.responseText;
|
var current = xmlhttprequest.responseText;
|
||||||
var instructionEnd;
|
var instructionEnd;
|
||||||
|
|
||||||
if (start == null) {
|
|
||||||
start = new Date().getTime();
|
|
||||||
startOffset = current.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((instructionEnd = current.indexOf(";", startIndex)) != -1) {
|
while ((instructionEnd = current.indexOf(";", startIndex)) != -1) {
|
||||||
|
|
||||||
// Start next search at next instruction
|
// Start next search at next instruction
|
||||||
@@ -383,8 +346,27 @@ function VNCClient(display) {
|
|||||||
|
|
||||||
var opcodeEnd = instruction.indexOf(":");
|
var opcodeEnd = instruction.indexOf(":");
|
||||||
|
|
||||||
var opcode = instruction.substr(0, opcodeEnd);
|
var opcode;
|
||||||
var parameters = instruction.substr(opcodeEnd+1).split(",");
|
var parameters;
|
||||||
|
if (opcodeEnd == -1) {
|
||||||
|
opcode = instruction;
|
||||||
|
parameters = new Array();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
opcode = instruction.substr(0, opcodeEnd);
|
||||||
|
parameters = instruction.substr(opcodeEnd+1).split(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're done parsing, handle the next response.
|
||||||
|
if (opcode.length == 0) {
|
||||||
|
|
||||||
|
if (isConnected()) {
|
||||||
|
delete xmlhttprequest;
|
||||||
|
handleResponse(nextRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Call instruction handler.
|
// Call instruction handler.
|
||||||
doInstruction(opcode, parameters);
|
doInstruction(opcode, parameters);
|
||||||
@@ -396,23 +378,6 @@ function VNCClient(display) {
|
|||||||
delete instruction;
|
delete instruction;
|
||||||
delete parameters;
|
delete parameters;
|
||||||
|
|
||||||
// If we're done parsing, handle the next response.
|
|
||||||
if (xmlhttprequest.readyState == 4 && isConnected()) {
|
|
||||||
|
|
||||||
// If we got the start time, do statistics.
|
|
||||||
if (start) {
|
|
||||||
var end = new Date().getTime();
|
|
||||||
var duration = end - start;
|
|
||||||
var length = current.length;
|
|
||||||
|
|
||||||
totalTime += duration;
|
|
||||||
totalTransferred += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete xmlhttprequest;
|
|
||||||
handleResponse(nextRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -425,17 +390,9 @@ function VNCClient(display) {
|
|||||||
|
|
||||||
function makeRequest() {
|
function makeRequest() {
|
||||||
|
|
||||||
// Calculate message limit as number of bytes likely to be transferred
|
|
||||||
// in one round trip.
|
|
||||||
var messageLimit;
|
|
||||||
if (totalTime == 0 || roundTripSamples == 0)
|
|
||||||
messageLimit = 10240; // Default to a reasonable 10k
|
|
||||||
else
|
|
||||||
messageLimit = Math.round(totalRoundTrip * totalTransferred / totalTime / roundTripSamples);
|
|
||||||
|
|
||||||
// Download self
|
// Download self
|
||||||
var xmlhttprequest = new XMLHttpRequest();
|
var xmlhttprequest = new XMLHttpRequest();
|
||||||
xmlhttprequest.open("GET", "instructions?messageLimit=" + messageLimit);
|
xmlhttprequest.open("GET", "instructions");
|
||||||
xmlhttprequest.send(null);
|
xmlhttprequest.send(null);
|
||||||
|
|
||||||
return xmlhttprequest;
|
return xmlhttprequest;
|
||||||
|
Reference in New Issue
Block a user