From 1c6b898f514ebf02b0ebe82052c9cf0c885f2b45 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 25 Jun 2010 00:51:45 -0700 Subject: [PATCH] Committing stream efficiency fixes from old SVN branch. --- .../guacamole/net/GuacamoleSession.java | 10 ++ .../net/output/InstructionStream.java | 88 +++++++++--------- .../sourceforge/guacamole/vnc/VNCClient.java | 28 +++--- guacamole/web/javascript/guacamole.js | 91 +++++-------------- 4 files changed, 93 insertions(+), 124 deletions(-) diff --git a/guacamole/src/net/sourceforge/guacamole/net/GuacamoleSession.java b/guacamole/src/net/sourceforge/guacamole/net/GuacamoleSession.java index b25c1e6cb..bcdc0915f 100644 --- a/guacamole/src/net/sourceforge/guacamole/net/GuacamoleSession.java +++ b/guacamole/src/net/sourceforge/guacamole/net/GuacamoleSession.java @@ -19,6 +19,7 @@ package net.sourceforge.guacamole.net; * along with this program. If not, see . */ +import java.util.concurrent.locks.ReentrantLock; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionBindingEvent; @@ -33,6 +34,7 @@ public class GuacamoleSession { private GuacamoleConfiguration config; private final HttpSession session; private Client client; + private ReentrantLock instructionStreamLock; private class SessionVNCClient extends VNCClient implements HttpSessionBindingListener { @@ -68,6 +70,7 @@ public class GuacamoleSession { config = new GuacamoleConfiguration(context); client = (Client) session.getAttribute("CLIENT"); + instructionStreamLock = (ReentrantLock) session.getAttribute("INSTRUCTION_STREAM_LOCK"); } } @@ -98,6 +101,9 @@ public class GuacamoleSession { session.setAttribute("CLIENT", client); + instructionStreamLock = new ReentrantLock(); + session.setAttribute("INSTRUCTION_STREAM_LOCK", instructionStreamLock); + } } @@ -120,4 +126,8 @@ public class GuacamoleSession { client.disconnect(); } + public ReentrantLock getInstructionStreamLock() { + return instructionStreamLock; + } + } diff --git a/guacamole/src/net/sourceforge/guacamole/net/output/InstructionStream.java b/guacamole/src/net/sourceforge/guacamole/net/output/InstructionStream.java index 1bd9bc2c2..c53161ff8 100644 --- a/guacamole/src/net/sourceforge/guacamole/net/output/InstructionStream.java +++ b/guacamole/src/net/sourceforge/guacamole/net/output/InstructionStream.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPOutputStream; +import java.util.concurrent.locks.ReentrantLock; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sourceforge.guacamole.Client; @@ -39,38 +40,13 @@ public class InstructionStream extends GuacamoleServlet { @Override protected void handleRequest(GuacamoleSession session, HttpServletRequest request, HttpServletResponse response) throws GuacamoleException { - // Instruction buffer - StringBuilder instructions = new StringBuilder(); + ReentrantLock instructionStreamLock = session.getInstructionStreamLock(); + instructionStreamLock.lock(); try { - // Query new update from VNC server - Client client = session.getClient(); - - 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"); + response.setContentType("text/plain"); + OutputStream out = response.getOutputStream(); // Compress if enabled and supported by browser if (session.getConfiguration().getCompressStream()) { @@ -84,22 +60,14 @@ public class InstructionStream extends GuacamoleServlet { // Use gzip if supported if (encoding.equals("gzip")) { response.setHeader("Content-Encoding", "gzip"); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - OutputStream zout = new GZIPOutputStream(bos); - zout.write(outputBytes); - zout.close(); - outputBytes = bos.toByteArray(); + out = new GZIPOutputStream(out); break; } // Use deflate if supported if (encoding.equals("deflate")) { response.setHeader("Content-Encoding", "deflate"); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - OutputStream zout = new DeflaterOutputStream(bos); - zout.write(outputBytes); - zout.close(); - outputBytes = bos.toByteArray(); + out = new DeflaterOutputStream(out); break; } @@ -109,13 +77,42 @@ public class InstructionStream extends GuacamoleServlet { } - response.setContentType("text/plain"); - response.setContentLength(outputBytes.length); + try { - // Use default output stream if no compression. - OutputStream out = response.getOutputStream(); - out.write(outputBytes); + // Query new update from VNC server + Client client = session.getClient(); + + // 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(); + response.flushBuffer(); + } catch (UnsupportedEncodingException e) { throw new GuacamoleException("UTF-8 not supported by Java.", e); @@ -123,6 +120,9 @@ public class InstructionStream extends GuacamoleServlet { catch (IOException e) { throw new GuacamoleException("I/O error writing to servlet output stream.", e); } + finally { + instructionStreamLock.unlock(); + } } diff --git a/guacamole/src/net/sourceforge/guacamole/vnc/VNCClient.java b/guacamole/src/net/sourceforge/guacamole/vnc/VNCClient.java index 52a0eeee1..2ef32cecf 100644 --- a/guacamole/src/net/sourceforge/guacamole/vnc/VNCClient.java +++ b/guacamole/src/net/sourceforge/guacamole/vnc/VNCClient.java @@ -1036,20 +1036,22 @@ public class VNCClient extends Client { synchronized (instructionLock) { if (instructions.size() == 0) { - try { - // Send framebuffer update request - synchronized (output) { - output.writeByte(MESSAGE_FRAMEBUFFER_UPDATE_REQUEST); - output.writeBoolean(!needRefresh); // Incremental - output.writeShort(0); // x - output.writeShort(0); // y - output.writeShort(frameBufferWidth); // width - output.writeShort(frameBufferHeight); // height - output.flush(); + if (blocking) { + try { + // Send framebuffer update request + synchronized (output) { + output.writeByte(MESSAGE_FRAMEBUFFER_UPDATE_REQUEST); + output.writeBoolean(!needRefresh); // Incremental + output.writeShort(0); // x + output.writeShort(0); // y + output.writeShort(frameBufferWidth); // width + 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 diff --git a/guacamole/web/javascript/guacamole.js b/guacamole/web/javascript/guacamole.js index 0ef8b03a6..95ea0db60 100644 --- a/guacamole/web/javascript/guacamole.js +++ b/guacamole/web/javascript/guacamole.js @@ -202,21 +202,12 @@ function VNCClient(display) { mouse_xmlhttprequest.open("GET", "pointer?" + mouseEventBuffer); mouseEventBuffer = ""; // Clear buffer - var eventSendStart = new Date().getTime(); - // Once response received, send next queued event. mouse_xmlhttprequest.onreadystatechange = function() { - // Update round-trip-time figures - if (mouse_xmlhttprequest.readyState == 2) { - var eventSendEnd = new Date().getTime(); - totalRoundTrip += eventSendEnd - eventSendStart; - roundTripSamples++; - } - - if (mouse_xmlhttprequest.readyState == 4) { + if (mouse_xmlhttprequest.readyState == 4) sendPendingMouseEvents(); - } + }; mouse_xmlhttprequest.send(null); @@ -239,21 +230,6 @@ function VNCClient(display) { var clipboard_xmlhttprequest = new XMLHttpRequest(); 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); } @@ -310,12 +286,6 @@ function VNCClient(display) { showError(errors[errorIndex].getMessage()); } - // Data transfer statistics - var totalTransferred = 0; - var totalTime = 0; - var totalRoundTrip = 0; - var roundTripSamples = 0; - var clipboardHandler = null; this.setClipboardHandler = function(handler) { @@ -325,7 +295,6 @@ function VNCClient(display) { function handleResponse(xmlhttprequest) { - var start = null; // Start of download time var startOffset = null; var instructionStart = 0; @@ -345,7 +314,6 @@ function VNCClient(display) { if (nextRequest == null) nextRequest = makeRequest(); - start = new Date().getTime(); startOffset = 0; } @@ -366,11 +334,6 @@ function VNCClient(display) { var current = xmlhttprequest.responseText; var instructionEnd; - if (start == null) { - start = new Date().getTime(); - startOffset = current.length; - } - while ((instructionEnd = current.indexOf(";", startIndex)) != -1) { // Start next search at next instruction @@ -383,8 +346,27 @@ function VNCClient(display) { var opcodeEnd = instruction.indexOf(":"); - var opcode = instruction.substr(0, opcodeEnd); - var parameters = instruction.substr(opcodeEnd+1).split(","); + var opcode; + 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. doInstruction(opcode, parameters); @@ -396,23 +378,6 @@ function VNCClient(display) { delete instruction; 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() { - // 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 var xmlhttprequest = new XMLHttpRequest(); - xmlhttprequest.open("GET", "instructions?messageLimit=" + messageLimit); + xmlhttprequest.open("GET", "instructions"); xmlhttprequest.send(null); return xmlhttprequest;