124

All,

HTML5 Rocks has a nice beginner tutorial on Server-sent Events (SSE):

http://www.html5rocks.com/en/tutorials/eventsource/basics/

But, I don't understand an important concept - what triggers the event on the server that causes a message to be sent?

In other words - in the HTML5 example - the server simply sends a timestamp once:

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));

If I were building a practical example - e.g., a Facebook-style "wall" or a stock-ticker, in which the server would "push" a new message to the client every time some piece of data changes, how does that work?

In other words... Does the PHP script have a loop that runs continuously, checking for a change in the data, then sending a message every time it finds one? If so - how do you know when to end that process?

Or - does the PHP script simply send the message, then end (as appears to be the case in the HTML5Rocks example)? If so - how do you get continuous updates? Is the browser simply polling the PHP page at regular intervals? If so - how is that a "server-sent event"? How is this different from writing a setInterval function in JavaScript that uses AJAX to call a PHP page at a regular interval?

Sorry - this is probably an incredibly naive question. But none of the examples I've been able to find make this clear.

[UPDATE]

I think my question was poorly worded, so here's some clarification.

Let's say I have a web page that should display the most recent price of Apple's stock.

When the user first opens the page, the page creates an EventSource with the URL of my "stream."

var source = new EventSource('stream.php');

My question is this - how should "stream.php" work?

Like this? (pseudo-code):

<?php
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
    function sendMsg($msg) {
        echo "data: $msg" . PHP_EOL;
        echo PHP_EOL;
        flush();
    }

    while (some condition) {
        // check whether Apple's stock price has changed
        // e.g., by querying a database, or calling a web service
        // if it HAS changed, sendMsg with new price to client
        // otherwise, do nothing (until next loop)
        sleep (n) // wait n seconds until checking again
    }
?>

In other words - does "stream.php" stay open as long as the client is "connected" to it?

If so - does that mean that you have as many threads running stream.php as you have concurrent users? If so - is that remotely feasible, or an appropriate way to build an application? And how do you know when you can END an instance of stream.php?

My naive impression is that, if this is the case, PHP isn't a suitable technology for this kind of server. But all of the demos I've seen so far imply that PHP is just fine for this, which is why I'm so confused...

Script47
  • 14,230
  • 4
  • 45
  • 66
mattstuehler
  • 9,040
  • 18
  • 78
  • 108
  • That's the part that a developer must code on their own. The means of obtaining the data are via websockets/long polling etc. however the trick is *what* triggers the event. Personally, I've experimented with a few approaches and one approach that I liked (but it wasn't *that* fail-safe) was making MySQL trigger a console program every time something was inserted in a specific table. The console program would receive the info about changed/inserted record and it'd send notification to the corresponding user via WebSockets. Basically I had a PHP daemon waiting to send messages around. – N.B. Jan 28 '13 at 15:24
  • One problem with this, SSE is not supported by IE :-/ Also I would read this http://www.prodigyproductionsllc.com/articles/programming/javascript/avoid-using-eventsource-server-sent-events/ I think he's using a port to avoid the too-many-children problem but overall looks like his recommendation is to avoid SSE. Looks like way more trouble than it's worth, IMO. – PJ Brunet Nov 07 '13 at 20:34
  • Currently not supported by IE11 or Android Browser http://caniuse.com/eventsource – PJ Brunet Nov 07 '13 at 20:41
  • My suggestion is DO NOT use the "while" loop, instead you should use "for" loop. Because the sleep() time is not calculated in the php max execution time, thus means the while loop will run for a way too long time before the whole script reach max execution time, and that will cause the php process take your memory for long time. If your sleep time is 3 seconds, I suggest a 100 times for loop is enough, that is 5 minutes, and after 5 minutes, php script will peacefully stop, and client can start a new php process if necessary. – Zhang Buzz Jul 08 '16 at 07:07
  • 1
    If some one in need of sse php code: https://github.com/shahzadthathal/server-sent-events-php-example – Muhammad Shahzad Jul 28 '16 at 11:23
  • 7
    I had the same question and I think I deeply understand what you mean by _**what triggers the event on the server...**_. When you create an object of `EventSource('stream.php')`, the client opens a connection with `stream.php` which is like calling it by ajax. **THIS** connection triggers your server side code and keeps the connection open as long as your server side code has something to say. Then the connection closes and after a short delay (3 sec in chrome I think) client reopens the connection which triggers your `stream.php` file again. – Ahmad Maleki Jul 16 '17 at 05:37
  • I think for high traffic websites with PHP backend SSE is not a good solution is server will run out of resources, what's your idea after 4 years, have you implemented it and has it worked well for you? – Yuseferi Jan 13 '18 at 07:23
  • Could you please share your experience with us? Did you finally found any good and complete answer for your questions? – Reza Amya Oct 12 '19 at 21:51

5 Answers5

36

Server-sent events are for realtime update from the server-side to the client-side. In the first example, the connection from the server isn't kept and the client tries to connect again every 3 seconds and makes server-sent events no difference to ajax polling.

So, to make the connection persist, you need to wrap your code in a loop and check for updates constantly.

PHP is thread-based and more connected users will make the server run out of resources. This can be solved by controlling the script execution time and end the script when it exceed an amount of time (i.e. 10mins). The EventSource API will automatically connect again so the delay is in a acceptable range.

Also, check out my PHP library for Server-sent events, you can understand more about how to do server-sent events in PHP and make it easier to code.

Licson
  • 2,231
  • 18
  • 26
  • 8
    Could you elaborate on "This can be solved by controlling the script execution time and end the script when it exceed an amount of time"? If you have an excessive number of users, would it really improve resource usage much by closing the connection since the user would just connect again in 3 seconds? – Luke Oct 20 '14 at 19:09
  • Probably it is about the `while` loop not running forever. – Avatar Feb 20 '22 at 06:38
  • 1
    If the number of user doesn't change, I suspect re-establishing a connection every 10 minutes, won't change the number of threads in use - maybe placing a "if ( connection_aborted() ) break;" in your "while(true){" loop would do the trick of stopping the loop when the user moves away from the website. – will Apr 23 '22 at 23:32
36

"...does "stream.php" stay open as long as the client is "connected" to it?"

Yes, and your pseudo-code is a reasonable approach.

"And how do you know when you can END an instance of stream.php?"

In the most typical case, this happens when the user leaves your site. (Apache recognizes the closed socket, and kills the PHP instance.) The main time you might close the socket from the server-side is if you know there is going to be no data for a while; the last message you send the client is to tell them to come back at a certain time. E.g. in your stock-streaming case, you could close the connection at 8pm, and tell clients to come back in 8 hours (assuming NASDAQ is open for quotes from 4am to 8pm). Friday evening you tell them to come back Monday morning. (I have an upcoming book on SSE, and dedicate a couple of sections on this subject.)

"...if this is the case, PHP isn't a suitable technology for this kind of server. But all of the demos I've seen so far imply that PHP is just fine for this, which is why I'm so confused..."

Well, people argue that PHP isn't a suitable technology for normal web sites, and they are right: you could do it with far less memory and CPU cycles if you replaced your whole LAMP stack with C++. However, despite this, PHP powers most of the sites out there just fine. It is a very productive language for web work, due to a combination of a familiar C-like syntax and so many libraries, and a comforting one for managers as plenty of PHP programmers to hire, plenty of books and other resources, and some large use-cases (e.g. Facebook and Wikipedia). Those are basically the same reasons you might choose PHP as your streaming technology.

The typical setup is not going to be one connection to NASDAQ per PHP-instance. Instead you are going to have another process with a single connection to the NASDAQ, or perhaps a single connection from each machine in your cluster to the NASDAQ. That then pushes the prices into either a SQL/NoSQL server, or into shared memory. Then PHP just polls that shared memory (or database), and pushes the data out. Or, have a data-gathering server, and each PHP instance opens a socket connection to that server. The data-gathering server pushes out updates to each of its PHP clients, as it receives them, and they in turn push out that data to their client.

The main scalability issue with using Apache+PHP for streaming is the memory for each Apache process. When you reach the memory limit of the hardware, make the business decision to add another machine to the cluster, or cut Apache out of the loop, and write a dedicated HTTP server. The latter can be done in PHP so all your existing knowledge and code can be re-used, or you can rewrite the whole application in another language. The pure developer in me would write a dedicated, streamlined HTTP server in C++. The manager in me would add another box.

Darren Cook
  • 27,837
  • 13
  • 117
  • 217
  • As each Apache connection process consume memory, will it be better to use Nginx instead? – Zhang Buzz Jul 08 '16 at 00:01
  • @ZhangBuzz Where I've said Apache+PHP it really means "web-server + PHP process", so basically no difference with using a different web server. – Darren Cook Jul 08 '16 at 07:07
  • Maybe servers like these? https://github.com/hoaproject/Eventsource or https://github.com/hhxsv5/php-sse – Enrique Apr 24 '19 at 21:10
  • Also note that Nginx could be much more efficient for this because use less memory: https://blog.webfaction.com/2008/12/a-little-holiday-present-10000-reqssec-with-nginx-2/ – Enrique Apr 24 '19 at 21:28
4

I have notice that the sse techink sends every couple of delay data to the client (somtething like reversing the pooling data techink from client page e.x. Ajax pooling data.) so to overcome this problem i made this at a sseServer.php page :

<?php
        session_start();
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache'); // recommended to prevent caching of event data
        require 'sse.php';
        if ($_POST['message'] != ""){
                $_SESSION['message'] = $_POST['message'];
                $_SESSION['serverTime'] = time();
        }
        sendMsg($_SESSION['serverTime'], $_SESSION['message'] );
?>

and the sse.php is :

<?php
function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}
?>

Notice that at the sseSerer.php i start a session and using a session variable! to overcome the problem.

Also i call the sseServer.php via Ajax (posting and set value to variable message) every time that i want to "update" message.

Now at the jQuery (javascript) i do something like that : 1st) i declare a global variable var timeStamp=0; 2nd) i use the next algorithm :

if(typeof(EventSource)!=="undefined"){
        var source=new EventSource("sseServer.php");
        source.onmessage=function(event)
        if ((timeStamp!=event.lastEventId) && (timeStamp!=0)){
                /* this is initialization */
                timeStamp=event.lastEventId;
                $.notify("Please refresh "+event.data, "info");
        } else {
                if (timeStamp==0){
                         timeStamp=event.lastEventId;
                }
        } /* fi */

} else {
        document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events...";
} /* fi */

At the line of : $.notify("Please refresh "+event.data, "info"); is there that you can handle the message.

For my case i used to send an jQuery notify.

You may use POSIX PIPES or a DB Table instead to pass the "message" via POST since the sseServer.php does something like an "infinite loop".

My problem at the time is that the above code DOES NOT SENDS THE "message" to all clients but only to the pair (client that called the sseServer.php works as individual to every pair) so i'll change the technik and to a DB update from the page that i want to trigger the "message" and then the sseServer.php instead to get the message via POST it will get it from DB table.

I hope that i have help!

c.chasapis
  • 41
  • 1
3

This is really a structural question about your application. Real-time events are something that you want to think about from the beginning, so you can design your application around it. If you have written an application that just runs a bunch of random mysql(i)_query methods using string queries and doesn't pass them through any sort of intermediary, then many times you won't have a choice but to either rewrite much of your application, or do constant server-side polling.

If, however, you manage your entities as objects and pass them through some sort of intermediary class, you can hook into that process. Look at this example:

<?php
class MyQueryManager {
    public function find($myObject, $objectId) {
        // Issue a select query against the database to get this object
    }

    public function save($myObject) {
        // Issue a query that saves the object to the database
        // Fire a new "save" event for the type of object passed to this method
    }

    public function delete($myObject) {
        // Fire a "delete" event for the type of object
    }
}

In your application, when you're ready to save:

<?php
$someObject = $queryManager->find("MyObjectName", 1);
$someObject->setDateTimeUpdated(time());
$queryManager->save($someObject);

This is not the most graceful example but it should serve as a decent building block. You can hook into your actual persistence layer to handle triggering these events. Then you get them immediately (as real-time as it can get) without hammering your server (since you have no need to constantly query your database and see if things changed).

You obviously won't catch manual changes to the database this way - but if you're doing anything manually to your database with any frequency, you should either:

  • Fix the problem that requires you to have to make a manual change
  • Build a tool to expedite the process, and fire these events
Colin M
  • 13,010
  • 3
  • 38
  • 58
  • 4
    Colin - thanks for your answer. My fault - my question isn't clear - but this isn't really what I'm asking. What I *meant* to ask is this... If you're using PHP as your "server" - does the PHP script you call from the EventSource in your client need to run the *entire time* the client is connected to it? Would that mean that, if you have 1,000 concurrent users, you have 1,000 separate threads running 1,000 concurrent instances of your PHP script? Is that feasible? And, how would you know when to **end** the php script (assuming it's looping to stay "alive")? – mattstuehler Jan 28 '13 at 17:43
-9

Basically, PHP is not suitable techonology for this sort of things. Yes you can make it work, but it will be a disaster on highload. We run stockservers that send stock-change signals via websockets to dozens thousends users - and If we'd use php for that... Well, we could, but those homemade cycles - is just a nightmare. Every single connection will make a separate process on server or you have to handle connections from some sort of database.

Simply use nodejs and socket.io. It will let you easily start and have a running server in couple days. Nodejs has own limitations also, but for websockets (and SSE) connections now its the most powerfull technology.

And also - SSE is not that good as it seems. The only advantage to websockets - is that packets are being gzipped natively (ws is not gzipped), but on the downside is that SSE is one-side connection. You user, if he wants to add another stock symbol to subscripton, will have to make ajax request (including all troubles with origin control and the request will be slow). In websockets client and sever communicate both ways in one single opened connection, so if user sends a trading signal or subscribes to quote, he just send a string in already opened connection. And it's fast.

Prosto Trader
  • 3,471
  • 3
  • 31
  • 52
  • 3
    You can use React.php basicly the same way as the event-loop in node.js. – Matěj Koubík Dec 07 '16 at 13:41
  • 6
    Although it's good to tell that PHP isn't the best choice, I think you should at least include something OP asked for. –  Feb 01 '17 at 07:07