Sending filler data on WebSocket - javascript

I have JavaScript sending the coordinates of my mouse over the window via a WebSocket connection.
var webSocket = new WebSocket('ws:// ...');
$(window).mousemove(function (evt) {
webSocket.send(evt.pageX + ' ' + evt.pageY);
});
Then, PHP, my backend, is simply listening for these coordinates and sending to everyone who is connected the position of the cursor.
//whenever someone sends coordinates, all users connected will be notified
foreach ($webSocket->getUsers() as $user)
$user->send($xpos . ' ' . $ypos);
JavaScript gets these numbers and moves a red square based on this point.
//when PHP notifies a user, the square is updated in position
$('.square').css({
left: xpos,
top: ypos
});
The end product looks like this:
Now the issue is that it's very laggy, but I've found a way to combat this. I've added a interval for the JavaScript -- which just sends filler data every 50 milliseconds.
setInterval(function() {
webSocket.send('filler data');
}, 50);
Surprisingly, the change made it much smoother:
I've noticed how the left-side (where the mouse is being moved from), is always smooth, and I'm guessing that because the left window is always sending data, the connection is being kept smoother whereas the right window is only receiving data.
I've tried:
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, TCP_NODELAY, 1);
var_dump(socket_get_option($socket, SOL_SOCKET, TCP_NODELAY));
The output was int(-1), and it seems that Nagle's algorithm is still present in my application.
Somewhat related, Does setting TCP_NODELAY affect the behaviour of both ends of the socket?, this might be the issue of JavaScript purposely delaying the packets?
Why does sending something make it smoother?
Is there a way I can speed this up, without having to send useless data?

Your issue has to do with Nagle's Algorithm: Small packets of data are waiting for each other.
to disable this set TCP_NODELAY option using socket_set_option()
Regarding your edit, you are absolutely right. The client side is the problem here, because, although you can use javascript in your browser, windows systems, for example, do have a registry setting which by default enable Nagle on tcp.

Unfortunately this is a client-sided issue.
Using the registry editor (Start Orb -> Run -> regedit), we can enable TCPNoDelay as well set the TcpAckFrequency.
Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\
Furthermore, I have not found it possible or relevant to change TCP_NODELAY on SOCK_STREAM with contexts.
In the current situation, I have just decided to acknowledge (ACK) every packet that the server sends. I know this will be sacrificing bandwidth for latency.
webSocket.onmessage = function(evt) {
webSocket.send(''); //empty, but still includes headers
dispatch(evt);
}

Related

How do I fix player positioning being off in Node.JS and Socket.io

I'm making a multiplayer game and the way I am making player movement is whenever you click a key, that key is sent to all the other users in your lobby. They then change your position according to what key you clicked. For example if I clicked the 'W' key then it would be sent to everyone and they would all move my character forward. The reason I'm doing it this way is to save bandwidth and try to eliminate a lot of lag. However, this causes 2 problems. One of them being that the clients don't receive that keycode at the same time. Whenever I call Date.now() on JavaScript when I receive that key it is about 1 off from other clients. This will cause about a 3 pixel gap in between where it's supposed to be. I've already implemented Delta time so it looks the same on all framerates. The second problem is that I am highly trying to avoid hosting player positions on server unless necessary. This is a problem because if the players need to get the same position, the server can't give clients that data. To fix this, I made it so when I need a new position update, whether it's because I wasn't on the tab and missed a key or because my positioning is off, it would ask another client for their existing players positions. This solution only works when at least one client, excluding the person who asked, is on the tab.
I've tried using a setInterval to continuously change the players positions and match them up, but that made the players clip all over the place. I've also tried hosting the player positions on the server but it lags a lot and it won't be good if I have 1000 people on a server.
For client side I use p5js
This is the code that sends the server the key I clicked, whenever I click a key:
function keyPressed() {
if (gameStarted) {
if (keyCode === 122) {
return false;
}
if (currentKey.key != null) {
//if (currentKey.on == false || currentKey.key != keyCode) {
currentKey.key = keyCode;
currentKey.on = true;
socket.emit('newKeyCode',{key:currentKey.key,on:true});
//}
}
else {
currentKey.key = keyCode;
currentKey.on = true;
socket.emit('newKeyCode',{key:currentKey.key,on:currentKey.on});
}
}
}
function keyReleased() {
if (gameStarted) {
if (currentKey.key != null) {
currentKey.key = keyCode;
currentKey.on = false;
socket.emit('newKeyCode',{key:currentKey.key,on:currentKey.on});
}
}
}
This is the code that is on the server for whenever I tell the server I pressed a new key:
socket.on('newKeyCode',function(data) {
var lobby = LOBBY_DATA[PLAYER_LIST[socket.id].lobby];
if (lobby != null && lobby != undefined) {
//console.log(data);
for (var i in lobby.players) {
SOCKET_LIST[lobby.players[i].id].emit('newKeyFromClient', {id:socket.id,name:PLAYER_LIST[socket.id].user,key:data.key,on:data.on});
}
}
});
And this is the client code that is ran when I get a new key from the server:
socket.on('newKeyFromClient',function(data) {
socket.emit('receivedKey');
console.log(Date.now());
if (gameStarted) {
changePlayerDirections(data.key, data.on, data.id);
}
});
My goal is to make it so the clients both have the exact same player positioning, you can see for yourself at My game. Once you register and login, click on the Play button in the top left, then duplicate 3 more tabs so you have a total of 4, create a new lobby on one of them and split your computer screen into 4 windows so you can see all of them at once, then join your lobbies on all the windows. Once there are 4 people in your lobby, click Start Game on your host window. This will put you into the game where you can move around with WASD and you'll see it move on the other clients screens. However, it's gonna be a bit off and that's the problem. I want it to be dead on and exact on every single screen.
EDIT:
Sorry if there are rapid changes in the server, I'm working on it right now.
I guess you have already read articles on multiplayer: using UDP or TCP, frame rates for sending receiving datas, simulating game on server and syncing, etc. I will just write my experience.
I made a multiplayer game exactly similar way (using node.js and socket.io sending key events). Testing on LAN was all smooth. Then I tested it on wifi and it was a disaster. Then I send position, and they started jumping around. Bigger problem was it got way random and slower to update.
I read few articles on multiplayer and decided to use UDP to update position. It got fast and on wifi it was negligibly jumpy. Now deploying on server, port started changing. I learned about NAT traversals and stuffs, but it was too vast to implement myself. One thing I found was, at least the port from server was constant : '3000'.
Then I revert back to TCP and tried simulating game on server and sync with client. I send key events and on third frame synced position from server to client. Not much better and I still had to sync game logic. Thats where I stopped.
There is a logic I want to try but have been lazing around. Send key event to server using UDP, simulate on server and sync using TCP but this time using timestamp to determine which value to prevail (server or client). And reducing the data to send, using array of integer only.

Will running jQuery.get too often cause performance issues for the browser?

So you understand what I'm trying to do, I've written a web page which shows events logged in MySQL as soon as they are inserted into the database (basically monitoring Windows & Mac logon/logoff on the network). Every time a new event is inserted the PHP script connects to a web socket and sends a message to all connected browsers to notify them of the new event. When the browsers receive the notification message they run jQuery.get("liveeventsearch.php", ...); to fetch the new event from the database (see javascript code below).
In a nutshell, when a web socket message is received fetch the new record from the database, append the result to a table and play a notification sound.
socket.onmessage = function(msg) {
if (msg.data == "#all new event") {
var newLastUpdate = new Date().toISOString().slice(0, 19).replace('T', ' ');
jQuery.get("liveeventsearch.php", <?php echo '{ Key:"' . $Value . '", etc... , lastupdate:lastUpdate }'; ?>, function(result) {
jQuery('#LiveResults tbody').append(result);
if (jQuery('#chkNotification-Audio').is(':checked') && result > "")
{
jQuery("#Notification-Audio").trigger("play");
}
lastUpdate = newLastUpdate;
});
}
};
My concern is that there are currently approx 1200 devices on the network and it is expected that most, if not all of them will logon/logoff within a 5 to 10 minute period in large clumps hourly with a few additional scattered here and there. So the browser (depending on the supplied search criteria) will likely receive a large number of web socket messages in a small period of time, if not simultaneously (and obviously fetch liveeventsearch.php that many times). Is this likely to cause a problem for the browser fetching results so frequently?
I can provide the code in liveeventsearch.php if necessary.
Alternate Methods
I had thought about adding something like this in the socket.onmessage function to reduce the frequency.
//[PSEUDO CODE]
if (currentTime > lastUpdate + 3 seconds)
{
jQuery.get(...);
}
But then the last set of events will not appear until another web socket message is received which could be a lot longer than 3 seconds. I could possibly use a timer instead, but that kind of defeats the object of having a web socket providing 'live' updates.
Another option I thought of is to create a new MySQL table (e.g. liveUpdates) which contains only an ID field. Then run a cron job every X seconds which inserts a new ID in that table (or run a a script on the server with a continuous loop doing the same thing?). My events table could then have an additional field tying each event to the latest liveUpdates.ID and the cron job could send the web socket message each time a new update ID was created instead of every time an event is logged. But this again would have the same effect as using a timer.

Server saturation with Ajax calls

I'm using PHP over IIS 7.5 on Windows Server 2008.
My web application is requesting repeatedly with Ajax in the background 3 different JSON pages:
page 1 Every 6 seconds
page 2 Every 30 seconds
page 3 Every 60 seconds
They retrieve data related with the current state of some tables. This way I keep the view updated.
Usually I have no much trouble with it, but lately I saw my server saturated with hundreds of unanswered requests and I believe the problem can be due to a delay in one of the request.
If page1, which is being requested every 6 seconds, needs 45 seconds to respond (due to slow database queries or whatever), then it seem to me that the requests start getting piled one after the other.
If I have multiple users connected to the web application at the same time (or with multiple tabs) things can turn bad.
Any suggestion about how to avoid this kind of problem?
I was thinking about using some thing such as ZMQ together with Sockets.io in the client side, but as the data I'm requesting doesn't get fired from any user action, I don't see how this could be triggered from the server side.
I was thinking about using some thing such as ZMQ together with Sockets.io in the client side...
This is almost definitely the best option for long-running requests.
...but as the data I'm requesting doesn't get fired from any user action, I don't see how this could be triggered from the server side.
In this case, the 'user action' in question is connecting to the socket.io server. This cut-down example is taken from one of the socket.io getting started docs:
var io = require('socket.io')(http);
io.on('connection', function(socket) {
console.log('a user connected');
});
When the 'connection' event is fired, you could start listening for messages on your ZMQ message queue. If necessary, you could also start the long-running queries.
I ended up solving the problem following the recommendation of #epascarello and improving it a bit if I get no response in X time.
If the request has not come back, do not send another. But fix the serverside code and speed it up.
Basically I did something like the following:
var ELAPSED_TIME_LIMIT = 5; //5 minutes
var responseAnswered = true;
var prevTime = new Date().getTime();
setInterval(function(){
//if it was answered or more than X m inutes passed since the last call
if(responseAnsswered && elapsedTime() > ELAPSED_TIME_LIMIT){
getData()
updateElapsedTime();
}
}, 6000);
function getData(){
responseAnswered = false;
$.post("http://whatever.com/action.json", function(result){
responseAnswered = true
});
}
//Returns the elapsed time since the last time prevTime was update for the given element.
function elapsedTime(){
var curTime = new Date().getTime();
//time difference between the last scroll and the current one
var timeDiff = curTime - prevTime;
//time in minutes
return (timeDiff / 1000) / 60;
}
//updates the prevTime with the current time
function updateElapsedTime(){
prevTime = new Date().getTime();
}
This is a very bad setup. You should always avoid polling if possible. Instead of sending request every 6 seconds from client to server, send data from server to the clients. You should check at the server side if there is any change in the data, then transfer the data to the clients using websockets. You can use nodejs at the server side to monitor any changes in the data.

Why is POST data sent from the (before)unload event not being received?

I'm entering my second day of web development, so be gentle!
I'm trying to code a little web app which allows a user to visit a site and click/tap the screen to direct a dot on another screen. To do this I've set up a Django server to handle the data, I'm using Javascript to get the click/tap position, send that via POST data to the server, then draw it using Processing.js (my boss is big on Processing, and this is really a test for interactivity with Processing on a big screen via cellphones, etc).
I'm not a web developer so even this simple task has been quite the challenge. I was feeling quite comfortable up until this point. When a user loads the interaction web page, they are assigned an arbitrary user id for the app which lets them control their specific dot. I want that dot to be deleted when the user leaves the page. I plan on implementing a backup timeout later, but I'd like to have the instant delete first for the best experience, closest to expectations. (Our end goal I think is to have a nice big screen running a simple game, where people can walk by, use the QR code on the screen to launch the interaction site, and join the game. When they get bored they just close the site and that deletes them from the game screen, simple).
In my interaction page I have this code (apologies for poor variable names):
<body onload="load()" onbeforeunload="unload()">
<canvas id="canv" width="1000" height="1000"/>
<script type="text/jscript">
var userid;
function load() {
userid = Math.floor(Math.random()*1000) //this will of course be changed
var canv = document.getElementById('canv');
canv.addEventListener('click', onCanvasClick);
//$(window).unload(unload());
}
function onCanvasClick(event) {
var cx, cy;
var canvv = document.getElementById('canv');
canoffset = $(canvv).offset();
cx = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - Math.floor(canoffset.left);
cy = event.clientY + document.body.scrollTop + document.documentElement.scrollTop - Math.floor(canoffset.top) + 1;
cx = (((cx - 500) / 500) * 3);
cy = (((cy - 500) / 500) * 3);
var data = {
x: cx,
y: cy,
id : userid,
deleteMe : 0
};
$.post("", data);
}
function unload() {
//send remove code for this user
var data = {
x: 0,
y: 0,
id: userid,
deleteMe : 1
};
$.post("", data);
//alert("test");
}
</script>
This POST data is supposed to be sent to the Django server. The POST sent from the onCanvasClick function is successfully received, and I see the dots moving according to plan. However, the POST sent from the unload() function is not received, no matter if I use the beforeunload or unload event (or, as you can see commented out, the JQuery unload). However, the commented-out alert still fires.
What am I missing here? Is there a tried-and-true way to tell the server that a user has left the page?
There is no 100% dependable way to tell when someone has left your page. The best you can do is use some AJAX polling and assume that the user has left once the polling stops or perhaps use web sockets (not universally available) to keep a connection open.
For example - I'm browsing your site with my tablet, and I lose my Internet connection. beforeunload will not help you in that situation.
Basically you have a race condition which the browser normally wins.
Modern day browsers try to speed up the pages as fast as possible. People hate waiting for the next page to load so what they did was kill any request that is opened at unload/beforeunload. Older browsers would wait for a for loop, but they killed that.

Reduce Ajax requests

I'm making a chat script using jQuery and JSON, but my hosting suspends it due to 'resources usage limit'. I want to know if it is possible (and how) to reduce these requests. I read one question in which they tell something about an Ajax timeout, but I'm not very good at Ajax. The code is:
function getOnJSON() {
var from;
var to;
var msg_id;
var msg_txt;
var new_chat_string;
//Getting the data from the JSON file
$.getJSON("/ajax/end.emu.php", function(data) {
$.each(data.notif, function(i, data) {
from = data.from;
to = data.to;
msg_id = data.id;
msg_txt = data.text;
if ($("#chat_" + from + "").length === 0) {
$("#boxes").append('...some stuf...');
$('#' + from + '_form').submit(function(){
contactForm = $(this);
valor = $(this + 'input:text').val();
destinatary = $(this + 'input[type=hidden]').val();
reponse_id = destinatary + "_input";
if (!$(this + 'input:text').val()) {
return false;
}
else {
$.ajax({
url: "/ajax/end.emu.php?ajax=true",
type: contactForm.attr('method'),
data: contactForm.serialize(),
success: function(data){
responsed = $.trim(data);
if (responsed != "success") {
alert("An error occured while posting your message");
}
else {
$('#' + reponse_id).val("");
}
}
});
return false;
}
});
$('#' + from + '_txt').jScrollPane({
stickToBottom: true,
maintainPosition: true
});
$('body').append('<embed src="http://cdn.live-pin.com/assets/pling.mp3" autostart="true" hidden="true" loop="false">');
}
else {
var pane2api = $('#' + from + '_txt').data('jsp');
var originalContent = pane2api.getContentPane().html();
pane2api.getContentPane().append('<li id="' + msg_id + '_txt_msg" class="chat_txt_msg">' + msg_txt + '</li>');
pane2api.reinitialise();
pane2api.scrollToBottom();
$('embed').remove();
$('body').append('<embed src="http://cdn.live-pin.com/assets/pling.mp3" autostart="true" hidden="true" loop="false">');
}
});
});
}
The limit is of 600 reqs/5 min, and I need to make it almost each second. I had a year already paid and they have no refund, also I can't modify the server, just have access to cPanel.
Well, 600 req/5 min is pretty restrictive if you want to make a request/sec for each user. Essentially, that gives you that each user will make 60 req/min. Or 300/5 min. In other words, even if you optimize your script to combine the two requests to one, at maximum you can have two users at your site ;) Not much I guess...
You have two options:
Stick with making a chat system through Ajax requests and change the hosting provider. This might be actually cheaper if you don't have the skills to do 2.
Forget about making an Ajax request to poll and potentially another to push every second. Implement something around web sockets, long-polling or even XMPP.
If you go that route, I would look at socket.io for a transparent library that uses web sockets where they are supported and has fallbacks to long polling and others for the rest. For the XMPP-way, there is the excellent Strophe.js. Note that both routes are much more complex than your Ajax requests and will require a lot of server logic changes.
I don't think that checking each second is really a good idea, in my opinion for online chat 2/3 seconds check should be far enough.
To get less request, you can also add a check on the user activity in client side, if the windows is inactive you can lengthen the checking time, going back to 2/3 seconds when the user come back active, that will allow you to save resources and requests / minutes
I'm working on a project right now that requires keeping the UI in sync with server events. I've been using long polling which does indeed reduce the number of ajax calls, but then it put's the burden on the server to listen for the event that the client is interested in, which isn't fun either.
I'm about to switch over to socket.io which I will set up as a separate push service.
existing server --> pushes to sockt.io server --> pushes to subscribing client
ggozad's response is good, I also recommend web sockets. They work only with newer browser models, so if you want to make it available on all browsers you will need a small Flash bridge (Flash can communicate with sockets very easily and also it can call JavaScript functions and be called from JavaScript). Also, Flash offers P2P if you are interested. http://labs.adobe.com/technologies/cirrus/
Also, for server side you can look into Node.js if you are a JavaScript fan like me :)
To complete my response: there is no way to make an Ajax based chat in witch you are limited to 600 requests/5 min (2 requests/second), want to make a request/second and want more than two users.
Solution: switch to sockets or P2P.
I recommend you to call that paid service from the server side using a single thread (as an API proxy). You can still poll with 600 requests/5 min in this thread. Then every client do Ajax requests to poll or long-poll to your server API proxy without limitation.

Categories

Resources