sidorares / node-rfb2

rfb wire protocol client and server
MIT License
138 stars 27 forks source link

Server screen doesn't update until a new client connects. #8

Closed aaditmshah closed 5 years ago

aaditmshah commented 10 years ago

So I have a VNC server which can handle multiple clients. I connect to the server using a VNC viewer in view-only mode to see the output. Then I run the following node.js script to print some text in a text editor opened on the server side:

var rfb2 = require("rfb2");

var rfb = rfb2.createConnection({
    password: "secret",
    host: "example.com",
    port: 5900
});

rfb.on("connect", function () {
    sendKeys("Hello World!\n");

    setTimeout(function () {
        sendKeys("10 seconds.\n");
    }, 10000);

    setTimeout(function () {
        sendKeys("20 seconds.\n");
    }, 20000);

    setTimeout(function () {
        sendKeys("30 seconds.\n");
        sendKeys("Goodbye World!\n");
        rfb.end();
    }, 30000);
});

var shiftMap = {
    '!': '1',
    '@': '2',
    '#': '3',
    '$': '4',
    '%': '5',
    '^': '6',
    '&': '7',
    '*': '8',
    '(': '9',
    ')': '0'
};

function sendKeys(string) {
    string.split("").forEach(keyPress);
}

function keyPress(c) {
    var code = c.charCodeAt(0);

    if (isUpperCase(c) || shiftMap.hasOwnProperty(c)) {
        // 0xFFE1 is the X11 keysym for shift.
        rfb.keyEvent(0xFFE1, 1);
        rfb.keyEvent(code, 1);
        rfb.keyEvent(code, 0);
        rfb.keyEvent(0xFFE1, 0);
    } else {
        // 0xFF0D is the X11 keysym for enter.
        if (c === '\n' || c === '\r') code = 0xFF0D;
        rfb.keyEvent(code, 1);
        rfb.keyEvent(code, 0);
    }
}

function isUpperCase(c) {
    return c.toLowerCase() !== c;
}

When I run this program no output can be seen on the VNC viewer until the rfb.end is called, at which point the entire output is displayed at once.

In between if I connect to the server with a second instance of the VNC viewer then the output till then is displayed at once. After that everything works as expected.

What could be causing this problem? How to workaround it?

sidorares commented 10 years ago

Interesting. Which server are you using?

sidorares commented 10 years ago

With rfb2 as only connection ( x11vnc / ubuntu virtualbox ) everything works as expected

2 connectins - still ok ( first connect "chicken of the vnc" in view mode, then start script )

aaditmshah commented 10 years ago

I am using the Vine (OSXvnc) Server on Snow Leopard. The VNC viewer which I'm using is Flashlight VNC. From what I understand the server only sends framebuffer updates when a client requests for it. I suspect that the client doesn't receive the updates because it never requested for them.

Another possible problem could be that node-rfb2 doesn't send the keys immediately. I know that you flush the stream on every event:

RfbClient.prototype.pointerEvent = function(x, y, buttons)
{
    var stream = this.pack_stream;

    stream.pack('CCSS', [rfb.clientMsgTypes.pointerEvent, buttons, x, y]);
    stream.flush();
}

RfbClient.prototype.keyEvent = function(keysym, isDown)
{
    var stream = this.pack_stream;

    stream.pack('CCxxL', [rfb.clientMsgTypes.keyEvent, isDown, keysym]);
    stream.flush();
}

However on flush you only seem to be generating new data events:

UnpackStream.prototype.flush = function(stream)
{
    // TODO: measure performance benefit of
    // creating and writing one big concatenated buffer

    // TODO: check write result
    // pause/resume streaming
    for (var i=0; i < this.write_queue.length; ++i)
    {
         //stream.write(this.write_queue[i])
         this.emit('data', this.write_queue[i]);
    }
    this.write_queue = [];
    this.write_length = 0;
}

I don't see where you're actually flushing the stream. Plus node.js net.Socket objects don't even have a flush method. The do however have a setNoDelay method which can be used to disable Nagle's algorithm (which is enabled by default).

Nagle's algorithm buffers data before sending it off to ensure that small messages don't waste a lot of bandwidth because each TCP datagram is of at least 64 bytes. I don't see you calling setNoDelay anywhere:

$ grep -r setNoDelay node_modules/rfb2/
$ # no results
sidorares commented 10 years ago

yes, it's not real os flush, it's "combine all write requests into one buffer and do stream.write()". Can you try to add setNoDelay on your setup? Iou could be right about buffering, 'send key' is a very small packet. Also to verify - try to send more keys ( like, few 100 of them ). Requesting updates should not affect keys directly (you can receive zero rectangles but still able to send keys and mouse events) , but it might trigger buffer to be written

aaditmshah commented 10 years ago

I added the following lines to your createConnection function to disable Nagle's algorithm:

function createConnection(params)
{
    // first matched to list of supported by server will be used
    if (!params.security)
        params.security = [rfb.security.VNC, rfb.security.None];

    var stream;
    if (!params.stream) {
        if (params.in)
            stream = createRfbStream(params.rfbfile);
        else {
            if (!params.host)
                params.host = '127.0.0.1';
            if (!params.port)
                params.port = 5900;
            stream = net.createConnection(params.port, params.host);
        }
    } else {
        stream = params.stream;
    }

+    if (typeof stream.setNoDelay === "function") {
+        console.log("Disabling Nagle's algorithm.");
+        stream.setNoDelay();
+    }
+
    // todo: move outside rfbclient

On running the script again it disables Nagle's algorithm but the problem still persists. This rules out the buffering problem. I suspect that the server does indeed get the key events but it doesn't push the framebuffer updates to the other vnc clients. Is there any RFB extension to enable the server to automatically push updates to clients?

aaditmshah commented 10 years ago

I also sent a really long string (1234567890 * 10) on the 10 second and the 20 second marks. Still no difference. I'm pretty sure the server is receiving the events. It's just not sending the updates to the other VNC clients.

sidorares commented 10 years ago

Unfortunately Vine crashes on my system ( 10.8.5 ) and I don't have Snow Leopard mac. I guess it's a server issue.

You can't tell server to send updates every time screen content is damaged. However, when you request update server will wait (well, usually, protocol does not specify this) until content is changed. I usually request new screen update on every received one but you could have different strategy

khuongduybui commented 9 years ago

I'm having the same issue with TightVNC server on Ubuntu host. I have to put requestUpdate in my rect event handler and even though the request for update is sent very rapidly, the refresh rate on the screen is still extremely sluggish.

sidorares commented 9 years ago

hm, can't reproduce: I installed tightvnc in ubuntu14.10/virtualbox and successfully connected with https://github.com/sidorares/node-x11/tree/master/examples/vncviewer - it's quite responsive

khuongduybui commented 9 years ago

I'm having trouble with this VM http://sourceforge.net/projects/metasploitable/files/Metasploitable2/ using the sample code here: https://github.com/mgechev/js-vnc-demo-project (the vnc server on that VM is running on port 5900 with the password "password". Please don't mind other stuffs going on on that machine)