1

I'm writing a real time chat application using websocket with. I have a function that checks the database for new messages for a specific user. What I'm still missing is a way to infinitely call this method, so that the user can receive messages in real time.

Is there a way to run a background loop or something similar that can do the trick?

Thank you

michste93
  • 403
  • 1
  • 4
  • 6

3 Answers3

3

The worst thing that you could do is set all of your sockets to non-blocking. This will create an idle loop, where your WS server will constantly check everything, will eat through your CPU time, accomplish nothing meaningful, and make people mad at you.

The next worst thing is to block indefinitely. This will require clients to periodically send data, chewing through bandwidth, accomplish nothing meaningful, possibly put your server in a hung state while there are no clients connected, and make people mad at you.

Instead, use blocking, but set a timeout. This is a balancing act that depends on your system, and only you can decide the duration of the timeout, hopefully with a bit of data to back up your decision.

The best place to put a timeout is socket_select(). The code below will wait for a maximum of 1 second for new packets to come in, and when it gets bored, it'll do your DB stuff.

define("SOCKET_TIMEOUT", 1);

function tick() { /* Put your DB checking here. */ }

while (true) {
    $read = get_array_of_sockets_that_may_have_sent_a_packet();
    $write = $except = null; // We probably don't care about these.
    tick();
    $number_of_sockets_ready = socket_select($read, $write, $except, SOCKET_TIMEOUT);
    if ($number_of_sockets_ready === false) { ... } /* Something went horribly wrong. socket_strerror(socket_last_error()) is your friend here. */ 
    elseif ($number_of_sockets > 0) {
        // At least one socket is ready for socket_recv() or socket_read(). Do your magic here!
    }
}

Note that the timeout does not guarantee that tick() will be called every second, or only once per second. If a packet comes in immediately, and is handled trivially, it could potentially only be a few milliseconds between calls to tick(). On the other hand, if several packets come in and it's not trivial to handle each one, then it could be several seconds before tick() gets called again.

Ghedipunk
  • 1,229
  • 10
  • 22
  • Great it works but it checks for new messages only for one user at a time. I can't find a solution to check the database for every user connected. – michste93 Aug 31 '15 at 12:36
  • @michste93 `socket_select()` takes an array of socket handles as its arguments and modifies that array by reference. When `socket_select()` returns, you can loop over each of the socket handles that are left in the array. – Ghedipunk Aug 31 '15 at 14:28
  • Re-reading your follow up comment... You would check the database during `tick()` -- nothing magical about it, there's no need for `tick()` to handle only one connection/user at a time. You might want to have a table that holds active WS connections paired up to a site user, then you can use that table in your joins and where clause to match up a socket handle to a userID where there's a waiting notification. – Ghedipunk Aug 31 '15 at 16:14
  • @michste93 following the comment I left above, a hypothetical to point you in the direction I'm thinking: `SELECT n.userid, ws.connectionid, n.notification FROM notifications n LEFT JOIN ws_connections ws ON n.userid = ws.userid WHERE n.NotificationSeen = FALSE;` -- `foreach ($connectedUsers as $user) { if ($user->getConnectionId() == $result['connectionid']) { /* found the user with a notification. Send them a message using $user->getSocketHandle() */ }` -- Of course, it's your system, you're the expert, and making your own decisions is why you make the big bucks. – Ghedipunk Aug 31 '15 at 16:23
2

My preferred method is a simple setTimeout() call to repeatedly check the server for new messages.

var checkForNewMessages = window.setTimeOut(function(){
    $.ajax({
        url: 'getChatUpdates.php', 
        type: 'POST',
        data: {user_id: '<?php echo $_SESSION['user_id']; ?>' }
    }).done(function(r){
        // do stuff to display new messages here
    });
}, 5000);

As mentioned in the comments, depending on the architecture of your application, it may or may not be a security concern to have the user's ID visible in the source code. If so, another option would be to generate a disposable unique identifier for your user. I personally wouldn't, but like I said, it depends on the architecture you've set up.

Anyway, in theory, you could generate a GUID from the server side script and pass it to your AJAX, then have ajax use the GUID in it's next call. The GUID would change with every call that way an unauthenticaed person won't be able to simply duplicate that ajax request.

A simpler method might be to just get the user_id from the session server-side instead of passing it as a parameter.

I wrestled a bear once.
  • 22,983
  • 19
  • 69
  • 116
  • Good method, but your example is very risky (with the user_id). Perhaps provide your answer with some advice for security or at least a warning. – sanderbee Aug 28 '15 at 12:31
  • @sanderbee, I can see it being unnecessary, but I don't see how it could be all that risky. Anyway, updated my answer. – I wrestled a bear once. Aug 28 '15 at 13:08
  • 1
    Its not the part that it's visible in the code, but sending a userID by the request. Anyone can change this number from his own ID to a ID of someone else, and so read all his or her messages too. But anyway, nice edit:) – sanderbee Aug 28 '15 at 13:12
  • Ah, makes sense. I probably could have skipped those first two paragraphs then lol – I wrestled a bear once. Aug 28 '15 at 13:16
  • This also completely misses one of the main points of WebSockets: to prevent periodic AJAX polling. – Ghedipunk Aug 28 '15 at 17:37
0

I have never built anything like this myself. But how about writing a regular script that polls the database and then pushes the messages (I guess ideally these should be seperate components) and setting up a scheduled tasks that runs this script in the frequency you desire.

Architecturally, this is probably not the most elegant solution, as you will also find people arguing arguing around a similar question and related proposition here: Invoking a PHP script from a MySQL trigger

But seeing how you asked your question and what that indicates about your setup, this might do for a start.

Community
  • 1
  • 1
matt_jay
  • 1,241
  • 1
  • 15
  • 33