Showing posts with label MySQL. Show all posts
Showing posts with label MySQL. Show all posts

Sunday, 4 November 2018

Server Crash... Bad Memory

I've spent about three hours checking and sorting out the machine which went down, and come-what may I found problem after problem, but not with any of the software.  If your software checks out then there's an obvious other place to look... Your hardware.

My first step was to remove the drives, specifically my ZFS devices.  No difference, everything still broke.

I then set about systematically removing one piece of hardware at a time, first my quad ethernet card, no difference, then I removed the added dual SATA card still broke...

Finally, I removed all the memory and inserted just one stick...

For the first time the firmware of the motherboard reported something had changed (at boot) it knew the memory has reduced from 8GB to 2GB.  But then the grub boot was really fast, it came up and into the system very quickly.

Now, I had been getting MySQL Error 2013 for nearly ever query or SQL dump, with 2GB of RAM this didn't happen, sudo and all the programs now work again... I can therefore only assume one of the other sticks of RAM is corrupt in some manner.


So with a clean boot...


I was able to start pulling the data out...


I ran the server on soak in memtest for an hour and still no problem, there was ONLY a problem after MySQLd had started and 8GB of RAM was installed... Time to bin some of these sticks.

Thursday, 8 June 2017

Development : No Great Shakes at SQL

I was just handed a technical test, this was for a job interview, and I was a little... well I'll say surprised, but then shocked, by the reply, lets take a look...

I was handed these SQL statements, in several questions, and then told to summarise what I should see on any output of the various instructions....

create table customers (id INTEGER PRIMARY KEY, name Text, age INTEGER, weight REAL);
insert into customers values(73, "Brian", 42, 33);
insert into customers values(1, "Helen", 12, 12.5);
select * from customers;
SELECT name, CASE WHEN age > 18 THEN "adult" ELSE "minor" END "type" FROM customers;
create table orders (id INTEGER PRIMARY KEY, customer_id INTEGER, desc TEXT);
insert into orders values (0, 73, "Apples");
insert into orders values (1, 73, "Oranges");
insert into orders values (2, 1, "Bananas");
select * from orders;
SELECT customers.name, orders.desc FROM customers JOIN orders ON customers.id = orders.customer_id;

I drew up the two tables, their info, the results of the selects and the listing of each order by the last point.

The final question was then, "alter the final query so that only orders by customers named "Brian" are displayed.

My answer was this:

SELECT customers.name, orders.desc FROM customers JOIN orders ON customers.id = orders.customer_id where customers.id = (select id from customers where name = "Brian");

Now, I'm no great shakes at SQL, it's a tool to be used, not a mantra to adhere to, so I understood that I could also solve this problem thus:

SELECT customers.name, orders.desc FROM customers JOIN orders ON customers.id = orders.customer_id where customers.name = "Brian";

Both are valid results, and yes I admit mine results in another query (within the parenthesis) however, in my mind I wanted all parameters directly in a query to be the result of other queries, meaning I could place "Select customers where name is 'Brian'" into a function elsewhere for ease of maintenance, rather than have "Brian" hard coded into the query here.

I discussed this when myself and the technical reviewer went over my suggested solution, I explained "The query in the bracket should be stand along, either as a separate query or stored procedure".

He made a note on his clip board, and I thought nothing more about it.

Fast forward a fortnight, and this morning I heard back from this job, they said I was a very good candidate, but failed the technical test on one key point.  I looked at my notes, nothing looked that key.

I left it there, and went back to the recruiter but asked them for specific technical feedback.

The feedback I received, just an hour ago...

"Over thinks the solution to a problem" and they cited the above answer.

I am flabbergasted, it seems people really would rather my not give a valid answer, or perhaps don't want to discuss the decisions I passed through in order to reach my conclusion.

Either way, I think my solution is valid, my point about being able to move the latter query into its own location is valid, code maintenance is important to me, so being able to break down none-nonsensical ordering things like SQL statements is high on my radar, but to be told I am essentially "too verbose"... I'll be frank, it's pissed me off.

But hey, what do I know?... I'm no great shakes at SQL.

Tuesday, 14 February 2017

Programming : Python MySQL Connector Debug

Today, I was asked to look at a server for a friend, their problem... "It just stops working after a few days"... A few days turned into "between three and five".  Doing some mathematics I found they had between 125 and 350 unique visits to the server, each unique visit represents one customer or one remote unit of their fleet.

They relay their data from these to individual database instances on one MySQL Server, so there is about 30 customers each with many unique databases.

The problem?... Well, I find this very distressing, as they open one connection for each arriving remote client, use it and then they closed it... Right... RIGHT?!??!!

import mysql.connector

l_total = 0
while (True):
    # Count
    l_total += 1
    l_res = l_total % 100
    if l_res == 0:
        print (l_total)

    # Open a connection
    con = mysql.connector.connect(user='root', password='***', host='localhost', database='Pickles')
    cursor = con.cursor()

    # Query
    query = ("SELECT * FROM VeggiePatch")
    cursor.execute(query)

    # Retrieve the data
    cursor.fetchall()

    # Close the query cursor
    cursor.close()

    # Close the Connection
    con.close()

This is my test code based on the way their production code works, as having read the error log I see the problem is in the connector constructor and delves down into the networking code.

This of course crashes after around 33,000 cycles.

They're not willing to change their script "willy-nilly", I in fact think they're petrified I've found this problem.  Googling around I don't find any official explanation of this error, only anecdotal forum posts about the MySQL Connector not cleaning up after itself and so reusing the sockets fails over time.



The better solution is to garbage collect the connection each cycle...

import mysql.connector
import gc

l_total = 0
while (True):
    # Count
    l_total += 1
    l_res = l_total % 100
    if l_res == 0:
        print (l_total)

    # Open a connection
    con = mysql.connector.connect(user='root', password='***', host='localhost', database='Pickles')
    cursor = con.cursor()

    # Query
    query = ("SELECT * FROM Tickets")
    cursor.execute(query)

    # Retrieve the data
    cursor.fetchall()

    # Close the query cursor
    cursor.close()

    # Close the Connection
    con.close()
    con = None

    gc.collect()



I also tried to garbage collect each time I printed the the "total", each 100 passes, but this still crashed, the fixed loop here has so far done just under half a million cycles without issue....


Saturday, 21 September 2013

Banking, Maria DB and Panzer!

I have already briefly blogged about the identity thing with a certain bank, well the irony continues.

The DVLA identification I need to update my drivers licence is all the stuff the bank just turned down, so when I send all this valid, correct identification to the DVLA to get my licence, and then send the license onto the bank... I'll basically  have been chasing myself around in circles and wasting time...

Its all rather annoying.

In other news I'm moving forward on my personal project to create a game, and to host my world I'm setting up a persistent server, I had done a bunch of preliminary work on this in MySQL, but I've decided to go with Maria DB instead.  So I'm going to be putting together the basic "how to register" an account PHP & database.  Then I'll be linking to that with C (well C++).

I've got a login-server which will then pass them onto the hosted game world server.

On the very small scale I'm at, with just a few test accounts concurrently connected, I've got these various servers hosted on my beefy desktop PC as a series of Virtual machines, and I'm connected into it over the wireless and my wired hub from two laptops (and the Raspberry Pi).

And now onto Panzers, specifically Girlz Und Panzer... I found a playing of the first episode... and I've taken a look, before I order the DVD... Nop over to "Daily Motion" folks and search.

Its completely mental, absolutely mental, but that's Japan for you... (And I speak from experience, Oss).

The other "tankery" issue was my edict to only play TD's... it worked a treat, I earned over 1,000,000 credits, did my 15 mission wins in random games, got lots of fun... My Stug III went from a pooting 46% win rate to 49%.  And I generally had a damn good time.

However, come Monday a new mission arrived, "Flank them"... where you earn a gun laying drive (500,000 credits worth) for getting 15 wins in random missions in a TD from the list... Of course this swamped the Match Making with dozens of bloody Tank Destroyers.

In one game there were 7 Stugs, 4 Hellcats, 2 Hetzers and a T80... it was mental... No tank could move, no-one could breath, let alone play!  So, tho my plan for the weekend was ace fun, when that mission came out it ruined it.

I'm also still not enjoying the Tiger II at all, I just die... and die hard.  I'm clearly doing something wrong, but I can't place what I'm doing that's so bad.  I kind of wish my commander had sixth sense, but my performance is so bad they've only got 30% of their selected skill trained - and I didn't even pick Sixth Sense first for him as its a 100% kick-in.... Hey ho.  I'll figure that out.

Oh, whilst watching this video on Daily Motion, I notice that the ad-blocker count is already at 101 adverts... That's as compared to YouTube where you get 2 per page... 101 adverts!

Thursday, 3 January 2013

HTML5 SSE Chat Server Example (Full Code & Video)


My first post of the year, and I should say Happy New Year I suppose.  The year is already starting out better for you poor folks reading my non-sense blog, because I have an item I'm going to explain in detail for you, including videos and pictures, and this is how to set up a very simple anonymous chat system through a server to multiple browsers using HTML5's Server Side Eventing mechanisms to remove the need to write polling javascript code in your browser.

The reason I'm going to cover this in details is that though there are lots of examples and descriptions out there as to how to set about doing just this the example code given is sketchy in most cases, missing in many and does not actually accomplish much with the technology to plant the seed of actually putting it to use.  So, in the hope of setting a trend for the year I'm going to put this post together, take video and explain it all and hopefully you'll all be able to get to the same stage I'm at and have a working chat exchange program.

Prerequisites
Lets cover the prerequisites, you need a web server set up on a machine, in my example here I'm going to use apache2 on Ubuntu 12.04 LTS, with PHP5 as my server side scripting language.  The browser I'm going to use is Google's Chromium, because I like its debugging in the form of the javascript console.

The server I have has been set up as a blank server image on a VMWare virtual machine instance, and I've run the following commands to get it set up:

sudo apt-get install apache2 php5 libapache2-mod-php5
sudo /etc/init.d/apache2 restart
sudo apt-get install apache2 php5-mysql mysql-server
sudo /etc/init.d/apache2 restart
sudo apt-get install phpmyadmin

I allowed phpmyadmin to install and selected to install it into "apache2" at the prompt and I'm done, my passwords are all known to me and so whenever I've been asked for one I've typed it in, you will need do the same, and no, I don't want to know your passwords :)

I've gotten the server's IP (ifconfig) and from my desktop machine I've browses to its IP "http://192.168.2.101/phpmyadmin" and checked I can get onto the database.

Database
I'm using a database and a single table as an imtermediary, each browser connected to the chat page will insert lines of text "chat strings" and they will be inserted in time order into the table and then delivered to all the listening clients as they cycle around.

This processing of the chat potentially allows us to process and filter it for profanity, or prevent inappropriate discussions of any type we choose, before we insert it into the database.

We need to add a database with a table to the server, and so we're going to use this code:


(Don't worry this is an image, we're going to break down the SQL line by line).

CREATE DATBASE ChatSystem CHARACTER SET latin1 COLLATE latin1_general_ci;

This line actually creates the database for us, its called "ChatSystem" and uses the latin character set and sorts (collates) its columns in tables by index against the latin character set.  If you're intenting to use a different character set you'll have to hit your chose SQL server's documentation for their available character sets.  But latin1 is going to cover most of Western Europe, North and South America's and the Antipodes for this example.

USE ChatSystem;

This just selects the database we just created.

CREATE TABLE IF NOT EXISTS GeneralChat
(
    Id   BIGINT NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (Id),

    Tme     BIGINT NOT NULL,

    Text     VARCHAR(128)
)
Engine = InnoDb;

This instruction does pretty much as you would guess reading it, it creates the table with the three fields "Id", "Tme" and "Text".  Text is just the chat string we're exchanging, the field design allows for upto 128 characters so in our HTML presented to take chat strings in we must remember to limit the input box's length to allow only 128 or less characters.

Tme is actually time, I'm omitting the 'i' to avoid using a reserved word in anyones SQL implementation, the time we're going to take as the time the server receives the chat string.  The client browser is not going to have to send it, and its just going to be a number becuase our server side scripting language (PHP) gives us the command "time()" which returns the time as a single integer.

Finally Id is the order the chat strings are inserted, we're going to leave this as an auto incremented integer because then we need not worry about it in our SQL insert statement later, again like Tme the Id need not be worried about nor generated by the client browser.

I take all this SQL and run it through phpmyadmin's SQL intepreter and I get my new database and table ready for use.

Client Side - Chat.html
On the client I'm going to have a very simple page, which is an outer page with an input box and a "send" button, and then an inner frame (iframe) which is showing the messages as they come back from the server... I could make this all one page, but I'm splitting things up to keep the demonstration as simple as possible.

The outer page first, we need to have a table (yes I like tables you can use divs and spans all you like) which has two rows, the top row is going to show us chat arriving and contain our iframe and below we're going to have our input text box:

<table>
<tr>
<td>
<iframe src="chatstream.html"></iframe>
</td>
</tr>

<tr>
<td>
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<col width="85%">
<col width="15%">

<tr>
<td colspan=1>
<input id="ChatString"
type="text"
value=""
maxlength="48"
onkeypress="HandleKeyPress(event)">
</td>
<td colspan=1
onclick="UserSubmitChat()">
Send
</td>
</table>
</td>
</tr>
</table>

So we have several things we need now define, first the inner frame HTML "chatstream.html", which we'll cover in detail below.  And then two javascript functions:

"HandleKeypress(event)" and "UserSubmitChat()", the first function just checks whether we're handling the enter key and calls the second function, it looks like this:

function HandleKeyPress (event)
{
var l_chCode = ('charCode' in event) ? event.charCode : event.keyCode;
if ( l_chCode == 13 )
{
UserSubmitChat();
}
}

So the mor importand function is "UserSubmitChat()" which looks like this:

function UserSubmitChat ()
{
var l_textBox = document.getElementById ("ChatString");
if ( l_textBox )
{
InsertChatString(l_textBox.value);
l_textBox.value = "";
}
}

Very simply the function looks up the element called "ChatString" which is our input box (see above in the HTML) and if found it inserts the chat string, blanking the input box.

Note: One could optimise the page to take the "document.getElementById" call once and store the "l_textBox" throughout!

So, I'm simply taking the string content of the input box and calling yet another function, what is this "InsertChatString(value)" function doing?  Well, lets take a look:

function InsertChatString (p_ChatString)
{
m_ChatQueue.push(p_ChatString);
}

Well, it takes the string and pushes it onto the queue of strings going somewhere, this is important we want to order the chat we're posting and a queue is an easy way to achieve this, we push onto the back of the queue and pop off the front when we want to dispatch the string somewhere.

Few developers realise the queue functionality is built directly into the javascript array, so to allow the member "m_ChatQueue" to be a queue for us we simply have to create it as an empty array:

var m_ChatQueue = [];

So, all in all our javascript block now looks like this, with neat and tidy functions to handle key pressing, string extraction and pushing the string onto the queue.

<script language="javascript">
var m_ChatQueue = [];

function InsertChatString (p_ChatString)
{
m_ChatQueue.push(p_ChatString);
}

function UserSubmitChat ()
{
var l_textBox = document.getElementById ("ChatString");
if ( l_textBox )
{
InsertChatString(l_textBox.value);
l_textBox.value = "";
}
}

function HandleKeyPress (event)
{
var l_chCode = ('charCode' in event) ? event.charCode : event.keyCode;
if ( l_chCode == 13 )
{
UserSubmitChat();
}
}
</script>

But still the strings go no-where, what I have done is therefore rather than have an iframe or hidden on screen element posting (with an HTML form) the data I've decided to have a server side script page (php) which will take the chat string as a HTTP GET parameter.

This page is to be called "chatsubmission.php" and take the paramter "ChatString", so the URL for posting the chat string "HelloWorld" will be "chatsubmission.php?ChatString=HelloWorld".

When I open the chat posting page then I want to smooth out the posting of these strings to the server, as the user enters chat strings I don't want them to be paused out as it posts to the server I want things to flow.  So the user seamlessly queues the chat strings they enter, and they may have posted many before the page submits them all, but the submission goes on as a background task very fast without seeming to interrupt the user or their browser.

To achieve this I'm going to use a timer, and when the HTML loads I'll start it:

<script language="javascript">
var m_ChatTimer = null;

function StartChatTimer ()
{
StopChatTimer ();
m_ChatTimer = setInterval(ChatTimerMatured, 500);
}

function StopChatTimer()
{
if ( m_ChatTimer != null )
{
clearInterval(m_ChatTimer);
m_ChatTimer = null;
}
}
</script>

Note: All the functions shown all go in the same single "<script>" block on the same page, I'm splitting them up for this post only, and will post the whole page code at the bottom.

The HTML to start the timer is then:

<body onload="StartChatTimer()">

Now we have a timer maturing every 0.5 seconds, and its going to submit to some URL we we've defined all the strings of text in the m_ChatQueue.  What does this code look like?

function ChatTimerMatured ()
{
StopChatTimer ();

if ( m_ChatQueue.length > 0 )
{
while (m_ChatQueue.length > 0)
{
var l_value = m_ChatQueue.pop();

var l_URL = "chatsubmission.php?ChatString=" + l_value;
var l_HTTP = new XMLHttpRequest();
l_HTTP.open ("GET", l_URL, false);
l_HTTP.send (null);
}
}

StartChatTimer();
}

Let us just analyse this code, so we stop the chat timer, to prevent another timer mature happening whilst we're processing a potentially long list of strings of chat.  And then check the queue, if there are queued items we take one item off (pop) at a time and build the submission URL.

Armed with the URL, we then cheat, we use the javascript XMLHttpRequest object to get that URL, effectively then our server side page gets the request and parameters.  Much as if we had an HTML form and submitted it causing an HTTP Post.

Take a moment to look this code over, both above and in the full listing for "chat.html" at the bottom of the post.

Once all the strings are posted, or we're determined there's nothing to do, we start the chat timer again.  During this interlude the user may have posted more chat strings, but we smooth out the operation.  We could also temper the duration of the timer to better suit slow internet connections, or increase the responsiveness if necessary.

Server Side - ChatSubmission.php
So far we've taken the user input and cleverly posted it into the server, now we must see what the server side does with the chat string:

<?php

if ( !empty($_GET) )
{
require_once('dbFunctions.inc');

$l_ChatString = $_GET["ChatString"];
if ( InsertChat($l_ChatString) )
{
echo "Y";
}
else
{
echo "N";
}
}

?>

This is the complete "ChatSubmission.php" page called by the javascript XMLHttpRequest object we previously explored, the PHP page includes some functions (written by us) and then from the URL gets the "ChatString" parameter, if there is such a parameter it then calls the function "InsertChat".

The PHP function I have written for this looks like this:

function InsertChat ($p_ChatString)
{
$l_result = false;
$l_Connection = ConnectDatabase();
if ( $l_Connection != null )
{
$l_ServerTime = time();

$l_SQL = "INSERT INTO GeneralChat VALUES (null, $l_ServerTime, '" . $p_ChatString . "')";

mysql_query($l_SQL, $l_Connection);

CloseDatabase($l_Connection);

$l_result = true;
}

return $l_result;
}

It opens the database connection, builds the insert SQL and performs the insert, returning true upon success or otherwise false.  Very simple, and you may pick over the bones in the full listing.

Before we move on, we need to just see the "$l_ServerTime" and use of "null" in the SQL we insert with, the null is for the Id, which as you should recall we set as AUTO INCREMENT, and the time is the server time at which we receive the message.  This time will be of interest later.

So, there we have the first leg of our chat string's journey, from the text input box into a javascript queue and then on a timer maturation out to the server as a seamless posting and into the mysql database.

Note: You can use any database or server side language you want at this point!  Change the script around however you want and experiment!

Server Side Event - Second Leg
The second leg of our chat strings journey is to be from the database out to the screen, this is performed with the "chatstream.html" page, which is going to set up the client browser to listen for the server side events being pushed back.  This is the page which you'll see most demonstrated out there on the internet, most demo's cover this setting yp of the message reception, and many do it better than I.

There are basically three events you can handle with Server Side Events in HTML5, "open" which is the event of the connection to the server being established, "error" which is the event when something has gone wrong server side, and finally "message" which is a text based string of characters from the server.

What you choose to put into the event is up to you, there are examples of doing json, xml or just plain text.  For our chat system the message sent is going to be the actual raw chat string.  We're going to make sure the server sends one chat string per message.

So, lets take a look at our HTML, its very simple:

<html>

<head>

<script language="javascript">
...
</script>

</head>

<body onload="LoadPage()">

<table id="chatTable" border=1 width="100%">
<tr>
<td>Chat</td>
</tr>
</table>

</body>

</html>

So we have a table called "chatTable" and we call a function called "LoadPage" when we open the page in the browser, we can now define the javascript:

function LoadPage ()
{
m_ChatTable = document.getElementById("chatTable");

source = new EventSource('chatlist.php');
source.addEventListener ("message", ChatArrived, false);
}

Breaking this down, we grap the chatTable from the HTML and then set up the key SSE receiving object.  This "EventSource" points to a new server side php page (chatlist.php).

And then registers for the "message" arriving to point to javascript function called "ChatArrived":

function ChatArrived (event)
{
console.log(event.data);
}

For debugging, this function will suffice, but my full function takes the data string and appends it to the chatTable:

<script language="javascript">
var source = null;
var m_LastTime = 0;
var m_ChatTable = null;

function LoadPage ()
{
m_ChatTable = document.getElementById("chatTable");

source = new EventSource('chatlist.php');
source.addEventListener ("message", ChatArrived, false);
}

function InsertChat (p_ChatString)
{
if ( m_ChatTable != null )
{
var l_newRow = m_ChatTable.insertRow(m_ChatTable.rows.length);
var l_newCell = l_newRow.insertCell(0);
l_newCell.innerHTML = p_ChatString;
}
}

function ChatArrived (event)
{
InsertChat (event.data);
}
</script>

This therefore completes the HTML5 SSE loop back, so long as the "chatlist.php" serves up SSE events each time it sends a message it'll arrive on the page and append to our chat table.

Note: Some readers have forgotten this "chatstream.html" page we've just defined is inside the iframe from the first page, so we can see on the table the chat listing in the iframe and then the input box below.

Server Side - chatlist.php 
Our final page is the PHP page which lists the chat back to the clients, the design I've gone with is a loop which takes the time and then returns each line of chat between the time taken and "now" each loop.  Then it sleeps and repeats, it looks like this:

<?php

header("Content-Type: text/event-stream\n\n");
header("Cache-Control: no-cache");

require_once('dbFunctions.inc');

$l_LastTime = 0;
while ( true )
{
$l_LastTime = ListChat($l_LastTime);
sleep(1);
}
?>

The important parts for the HTML5 SSE mechanism are the two header lines, we define the header type to be "text/event-stream" and that the page is to use no cache on the client side.

Note: The two new lines "\n\n" on the Content-Type string are very important and should not be ommitted.

Again the PHP page uses the include file we've defined, which has its complete listing below, but we'll just cover the "ListChat" function now:

function ListChat ($p_LastTime)
{
$l_LastTime = $p_LastTime;

$l_Connection = ConnectDatabase();

if ( $l_Connection != null )
{
if ( $l_LastTime == 0 )
{
$l_LastTime = time();
}
$l_Time = time();

$l_sql = "SELECT * FROM GeneralChat WHERE Tme > " . $l_LastTime . " AND Tme <= " . $l_Time;

$l_result = mysql_query($l_sql, $l_Connection);
if ( $l_result )
{
$l_numRows = mysql_numrows($l_result);
if ( $l_numRows > 0 )
{
for ($l_i = 0; $l_i < $l_numRows; ++$l_i)
{
$l_LastTime = mysql_result($l_result, $l_i, "Tme");
$l_Message = mysql_result($l_result, $l_i, "Text");

sendMsg ($l_LastTIme, $l_Message);
}
}
}
CloseDatabase($l_Connection);
}

return $l_LastTime;
}

Important to the operation of this function, it takes the last time we checked for chat and returns the "now" time for the last message dispatched.  This lets us then query in the center for only those lines of chat we need to send.

The flow of the function simply opens the database, builds the SQL, performs the query and if there are results dispatches each row of results (each chat string) as an individual event to the client side.  Our client handles this in its "ChatArrived" function and posts each row onto the onscreen table.

We also specially make sure if the time of the last query was zero (i.e. the script just started running) we correct to "now" to prevent the whole history of the text being stremed back to the client.

The key piece of code however is this snippet:

for ($l_i = 0; $l_i < $l_numRows; ++$l_i)
{
$l_LastTime = mysql_result($l_result, $l_i, "Tme");
$l_Message = mysql_result($l_result, $l_i, "Text");

sendMsg ($l_LastTIme, $l_Message);
}

Each result takes its time, which we use as a value to send the string against, and the string, the sendMsg function then looks like this:

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

Again, many of the online examples of SSE give this style of function as their example, we add the Id to the PHP echo (which is going back down the text/event-stream to the client) and the "data: " line is the line which is passed to the "ChatArrived" function as the "event" parameter.

So if we put the code:

echo "id: 1" . PHP_EOL;
echo "data: HelloWorld" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();

Then the "ChatArrived" function would receive a parameter of "HelloWorld", not "data: " leading it!

And this folks is the whole example.... You can follow the flow now:


  • chat.html : Takes the input string, queues and then dispatches the strings
  • chatsubmission.php : Accepts the dispatched strings into the database
  • chatstream.html : Sets up the SSE event source and registers to handle messages from chatlist.php
  • chatlist.php : Sends the messages as they arrive into the database

What follows is a live stream of my performing all these steps with a Ubuntu laptop and a Ubuntu server (Recorded 1st Jan 2013):



/// chat.sql full listing ----------------------------------

  CREATE DATABASE ChatSystem
  CHARACTER SET latin1
COLLATE latin1_general_ci;

  USE ChatSystem;

  CREATE TABLE IF NOT EXISTS GeneralChat
  (
Id BIGINT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (Id),

Tme BIGINT NOT NULL,
Text VARCHAR(128)
  ) Engine = InnoDb;


/// chat.html full listing --------------------------------

<html>

<head>
<title>Chat</title>
<script language="javascript">
var m_ChatQueue = [];
var m_ChatTimer = null;

function InsertChatString (p_ChatString)
{
m_ChatQueue.push(p_ChatString);
}

function UserSubmitChat ()
{
var l_textBox = document.getElementById ("ChatString");
if ( l_textBox )
{
InsertChatString(l_textBox.value);
l_textBox.value = "";
}
}

function HandleKeyPress (event)
{
var l_chCode = ('charCode' in event) ? event.charCode : event.keyCode;
if ( l_chCode == 13 )
{
UserSubmitChat();
}
}

function StartChatTimer ()
{
StopChatTimer ();
m_ChatTimer = setInterval(ChatTimerMatured, 500);
}

function StopChatTimer()
{
if ( m_ChatTimer != null )
{
clearInterval(m_ChatTimer);
m_ChatTimer = null;
}
}

function ChatTimerMatured ()
{
StopChatTimer ();

if ( m_ChatQueue.length > 0 )
{
while (m_ChatQueue.length > 0)
{
var l_value = m_ChatQueue.pop();

var l_URL = "chatsubmission.php?ChatString=" + l_value;
var l_HTTP = new XMLHttpRequest();
l_HTTP.open ("GET", l_URL, false);
l_HTTP.send (null);
}
}

StartChatTimer();
}

</script>

<body onload="StartChatTimer()">

<table>
<tr>
<td>
<iframe src="chatstream.html"></iframe>
</td>
</tr>

<tr>
<td>
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<col width="85%">
<col width="15%">

<tr>
<td colspan=1>
<input id="ChatString"
type="text"
value=""
maxlength="48"
onkeypress="HandleKeyPress(event)">
</td>
<td colspan=1
onclick="UserSubmitChat()">
Send
</td>
</table>
</td>
</tr>
</table>

</body>

</html>

/// dbcredentials.inc -- Full Listing ----------------------

<?

$g_User = "root";
$g_Password = "********";   // Place your password here
$g_Database = "ChatSystem";

?>

/// dbfunctions.inc -- Full Listing -------------------------

<?

function ConnectDatabase ()
{
$l_Connection = null;
include ('dbCredentials.inc');

$l_Connection = mysql_connect ('localhost', $g_User, $g_Password);
if ( $l_Connection )
{
if ( !mysql_select_db($g_Database) )
{
mysql_close($l_Connection);
$l_Connection = null;
}
}

return $l_Connection;
}

function CloseDatabase ($p_Connection)
{
if ( $p_Connection != null )
{
mysql_close($p_Connection);
$p_Connection = null;
}
}

function InsertChat ($p_ChatString)
{
$l_result = false;
$l_Connection = ConnectDatabase();
if ( $l_Connection != null )
{
$l_ServerTime = time();

$l_SQL = "INSERT INTO GeneralChat VALUES (null, $l_ServerTime, '" . $p_ChatString . "')";

mysql_query($l_SQL, $l_Connection);

CloseDatabase($l_Connection);

$l_result = true;
}

return $l_result;
}

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

function ListChat ($p_LastTime)
{
$l_LastTime = $p_LastTime;

$l_Connection = ConnectDatabase();

if ( $l_Connection != null )
{
if ( $l_LastTime == 0 )
{
$l_LastTime = time();
}
$l_Time = time();

$l_sql = "SELECT * FROM GeneralChat WHERE Tme > " . $l_LastTime . " AND Tme <= " . $l_Time;

$l_result = mysql_query($l_sql, $l_Connection);
if ( $l_result )
{
$l_numRows = mysql_numrows($l_result);
if ( $l_numRows > 0 )
{
for ($l_i = 0; $l_i < $l_numRows; ++$l_i)
{
$l_LastTime = mysql_result($l_result, $l_i, "Tme");
$l_Message = mysql_result($l_result, $l_i, "Text");

sendMsg ($l_LastTIme, $l_Message);
}
}
}
CloseDatabase($l_Connection);
}

return $l_LastTime;
}

?>

/// ChatStream.html --- Full Listing ------------------------

<html>

<head>

<script language="javascript">
var source = null;
var m_LastTime = 0;
var m_ChatTable = null;

function LoadPage ()
{
m_ChatTable = document.getElementById("chatTable");

source = new EventSource('chatlist.php');
source.addEventListener ("message", ChatArrived, false);
}

function InsertChat (p_ChatString)
{
if ( m_ChatTable != null )
{
var l_newRow = m_ChatTable.insertRow(m_ChatTable.rows.length);
var l_newCell = l_newRow.insertCell(0);
l_newCell.innerHTML = p_ChatString;
}
}

function ProcessChat (p_ChatString)
{
InsertChat (p_ChatString);
}

function ChatArrived (event)
{
ProcessChat (event.data);
}
</script>

</head>

<body onload="LoadPage()">

<table id="chatTable" border=1 width="100%">
<tr>
<td>Chat</td>
</tr>
</table>

</body>

</html>