Committing stream efficiency fixes from old SVN branch.

This commit is contained in:
Michael Jumper
2010-06-25 00:51:45 -07:00
parent b4ec9d3ab4
commit 1c6b898f51
4 changed files with 93 additions and 124 deletions

View File

@@ -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;
}
} }

View File

@@ -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();
}
} }

View File

@@ -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

View File

@@ -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;