Hyvor Developer LogoHYVOR DEVELOPER
Tutorials
Subscribe
Ajax Long Polling with PHP
PHP
Feb 6, 2018 15 mins 1765
Advertisement

Ajax Long Polling with PHP

Need to create a real-time application with Javascript and PHP? Actually, there are some different ways to client-side to interact with the server in real time.

  • Short-Polling
  • Long-Polling
  • Web Sockets
  • Server-Sent Events (SSE)

Short Polling and SSE are not good to use for a website. You may use long polling or Web Sockets. Long polling is supported by the most of web browsers while web sockets do not support old browsers. However, long polling needs more CPU capacity than web sockets. Many websites such as Facebook, etc. use long polling. I will be showing you how to create a long polling script with PHP. Examples at the end will describe you advanced long-polling.

This article is divided into following sub-topics.

Introduction

The following image describes the concept of Long-Polling.

Long Polling

Explained...

  • First, we send an ajax request from the browser (using javascript) to the web server
  • Web server loops until it finds an update
  • After finding an update, the server stops looping and sends the response to the browser
  • Javascript catches the response and updates the webpage with DOM
  • Everything works asynchronously in real time

Writing the JS code

First, we have to send an ajax request from the browser to the server to accomplish our target. I assume that you have basic knowledge of using Ajax with Javascript. If you like, you can follow my tutorial on that topic to learn more.


var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
	if (this.readyState == 4 && this.status == 200) {
	 		// when everything goes fine...
	}
};
xhttp.open("GET", "long-polling.php", true);
xhttp.send();




$.ajax({
   url: 'long-polling.php',
   success: function(result) {
      // when everything goes fine...
   },
   type: 'GET'
});


What the above code does...

  • First, it creates new XMLHttpRequest object and saves it in variable xhttp.
  • Then it defines the onreadystatechange function. This will be executed each time the ready state of the ajax request is changed. I'll be explaining more about creating this function to handle the response in last few examples.
  • Next, it opens the request. Here I have used GET method. I prefer to use GET method because it is fast and works fine with long-polling. Mind not to send sensitive data with the request.
  • long-polling.php is the file we going to create in the next step.
  • Finally, it sends the request to the server.

There are several ways in jQuery to send Ajax Requests. Here I have used $.ajax function. Please refer jQuery's documentation to learn more. BTW, I'll be explaining more on creating the success function in last few examples.

The back-end

First of all, you should have a good knowledge of using following statements, functions, and tricks. Let's go through those kinds of stuff.

The Infinite Loop


<?php

while(true) {

	echo 'yes'; // this will be executed unlimited times

}


  • Long-polling is based on infinite loops.
  • It loops while true is true. That means forever!
  • This code can freeze your browser. So, don't try.

Ending the execution

There should be a way to go out from the infinite loop and send a response to the client. In the script below, I have shown 3 different ways to break loops.


<?php

while(true) {
	
	// escape from the infinite loop
	break; 

	// escape from multiple loops
	for ($i = 0; $i < 10000; $i++) {
		if ($foundAnUpdate) {
			break 2; // escapes from the for loop & the infinite loop
		}
	}

	// output a message and terminate the script 
	if ($foundAnUpdate) {
		exit(json_encode[
			'status' => 'success'
		]);
	}

}


  • The first type of break statement can be used to break (go out from) one loop. Then, the code which is immediately after the loop will be executed.
  • Adding any number after the break statement will break the number of loops defined. (In the above example, it's 2)
  • exit function can be used to output a message and terminate the script. The first parameter is the output. Here, a JSON encoded string is sent as the output.

Other useful functions

  • session_write_close
  • ignore_user_abort
  • set_time_limit
  • sleep / usleep
  • Session data can't be used in long polling. If you do, it will completely freeze the browser tab. Other requests sent to the same domain won't load. Adding session_write_close() function at the top of the script can cancel the usage of sessions in the script.
  • ignore_user_abort() function can be used to define whether you need to stop the script when browser request aborted or not.
  • set_time_limit() function can set the max execution time. If the parameter is zero, the script runs forever. Set a time limit if you are thinking about the health of your server.
  • sleep() and usleep() functions are very useful and can be used to make the server asleep for a while to make sure that the loop does not affect server's performances. I highly recommend you to use them.


<?php

session_write_close();
ignore_user_abort(true);
set_time_limit(0);


while(true) {
	
	// here goes the loop

	sleep(1);

}



Code Explanation

  • First I have canceled session writing on the script with session_write_close().
  • Then ignore_user_abort(true) makes the script aborted when the browser cancels the request. Set it to false if you need to run the script even the browser cancels the request.
  • set_time_limit(0) sets execution time to infinite. But, if a lot of users use your website, set it to 30-40 seconds. Otherwise, it will overload the server.
  • It's a really good practice to use sleep(1) function. It makes the execution delayed for 1 second. usleep() function can be used to set sleep time in milliseconds.

Examples

I have created two examples for you. You can download a .zip file of both examples at the end of this sub-topic.

1. Listening for a file update - The Cookie Method

First, I will create index.html which is the client-side file. It sends an ajax request to long-polling.php which is an Ajax Request Handler.



<html>
<head>
	<title>Listen to File Update</title>
</head>
<body>



<script type="text/javascript">
	
	function poll() {
		var ajax = new XMLHttpRequest();
		ajax.onreadystatechange = function() {
			if (this.readyState === 4 && this.status === 200) {
				if (this.status === 200) {
					try {
						var json = JSON.parse(this.responseText);
					} catch {
						poll();return;
					}
					

					if (json.status !== true) {
						alert(json.error);return;
					}

					var div = document.createElement("DIV");
					document.body.appendChild(div);
					div.innerHTML = 'time: ' + json.time + ', content: ' + json.content


					poll();
				} else {
					poll();
				}

			}
		}
		ajax.open('GET', 'long-polling.php', true);
		ajax.send();


	}
	
	poll();

</script>

</body>
</html>





<?php

session_write_close();
ignore_user_abort(false);
set_time_limit(40);

try {

	// lastUpdate cookie saves the file update time which was sent to the browser
	if (!isset($_COOKIE['lastUpdate'])) {
		setcookie('lastUpdate', 0);
		$_COOKIE['lastUpdate'] = 0;
	}

	$lastUpdate = $_COOKIE['lastUpdate'];
	$file = 'file.txt';

	if (!file_exists($file)) {
		throw new Exception('file.txt Does not exist');
	}

	while (true) {

		$fileModifyTime = filemtime($file);

		if ($fileModifyTime === false) {
			throw new Exception('Could not read last modification time');
		}

		// if the last modification time of the file is greater than the last update sent to the browser... 
		if ($fileModifyTime > $lastUpdate) {
			setcookie('lastUpdate', $fileModifyTime);

			// get file contents
			$fileRead = file_get_contents($file);

			exit(json_encode([
				'status' => true,
				'time' => $fileModifyTime, 
				'content' => $fileRead
			]));

		}

		// to clear cache
		clearstatcache();

		// to sleep
		sleep(1);
		
	}

} catch (Exception $e) {

	exit(
		json_encode(
			array (
				'status' => false,
				'error' => $e -> getMessage()
			)
		)
	);

}


This example is based on a cookie. In the first request, it creates a cookie named lastUpdate and sends it to the client's browser. This cookie saves the last file modification time of file.txt that was sent to the browser.

In the next request, it loops until the actual modification time of file.txt is greater than the lastUpdate cookie. If yes, it sends a JSON response to the browser. Javascript code in ajax request creates new <div> and adds some content to it.

2. Listening for a database update

Here we only create a cookie to save the user id. Timestamps and other data are saved in the database.

  • There are two tables, db_user_data (to save user data) and db_updating_table (to save new messages)
  • In the first request, the user cookie is created.
  • In the loop, it checks whether there are new rows in db_updating_table. If yes, sends column values of those rows to the browser.
  • There is update-other-table.php to update db_updating_table.
  • When a user is in index.html, and anyone requests update-other-table.php to create a new message (or a row), the first user sees the message in real-time.
  • I recommend you to download the .zip file and try it yourself. I have explained the code line by line with comments.

    In index.html only the onreadystatechange function is changed.

    
    function poll() {
    	var ajax = new XMLHttpRequest();
    	ajax.onreadystatechange = function() {
    		if (this.readyState === 4 && this.status === 200) {
    			if (this.status === 200) {
    				try {
    					var json = JSON.parse(this.responseText);
    				} catch {
    					poll();return;
    				}
    				
    
    				if (json.status !== true) {
    					alert(json.error);return;
    				}
    
    				var data = json.data;
    				for (var i = 0, len = data.length; i < len; i++) {
    					var x = data[i];
    
    					var div = document.createElement("DIV");
    					document.body.appendChild(div);
    					div.innerHTML = 'time: ' + x.time + ', content: ' + x.content;
    
    				}
    				poll();
    			} else {
    				poll();
    			}
    
    		}
    	}
    	ajax.open('GET', 'long-polling.php', true);
    	ajax.send();
    
    
    }
    
    poll();
    
    
    
    
    <?php
    
    session_write_close();
    ignore_user_abort(false);
    set_time_limit(40);
    
    try {
    	// connect to the db ($mysqli)
    	include_once '../config.php';
    
    	if (empty($_COOKIE['user'])) {
    		$user = rand(0,10000000);
    		// send to the browser
    		setcookie('user', $user);
    
    		// save in global variable
    		$_COOKIE['user'] = $user;
    
    		// add user to the database
    		$mysqli -> query("INSERT INTO db_user_data VALUES ($user, 0)");
    
    		// first request does not do anything than creating the cookie
    		exit();
    	}
    
    	// get the user value 
    	$user = $_COOKIE['user'];
    
    	while (true) {
    
    		// select new rows
    		$result = $mysqli -> query("SELECT t.id, t.content, t.time FROM db_updating_table t INNER JOIN db_user_data ud ON ud.last_sent_id < t.id WHERE ud.user = $user ORDER BY t.id");
    
    		if ($result && $result -> num_rows) {
    			$output = [];
    			$lastId = 0;
    			foreach ($result as $row) {
    				$output[] = [
    					'content' => $row['content'], 
    					'time' => $row['time']];
    
    				$lastId = $row['id'];
    			}
    			$mysqli -> query("UPDATE db_user_data SET last_sent_id = $lastId WHERE user = $user");
    
    
    			echo json_encode([
    				'status' => true,
    				'data' => $output
    			]);
    			exit;
    		}
    
    
    
    		// db queries are heavy. So 2 seconds
    		sleep(2);
    	}
    
    } catch (Exception $e) {
    
    	exit(
    		json_encode(
    			array (
    				'status' => false,
    				'error' => $e -> getMessage()
    			)
    		)
    	);
    
    }
    
    
    
    
    <?php
    include_once '../config.php';
    
    
    $content = !empty($_GET['content']) ? $_GET['content'] : 'No Message Defined';
    $time = time();
    
    $mysqli -> query("INSERT INTO db_updating_table (content,time) VALUES ('$content', $time)");
    
    
    

    Querying the database continuously needs a powerful server. If you have one, there's nothing wrong with doing this.

    Downloading the examples

    This zip has both above examples. To use the second example, first, import the .sql file to your MYSQL database and then change config.php with your hostname, username, password and the database.

    Conclusion

    I have shown you how to use long polling different ways. I hope you enjoyed this tutorial. If you have any question, suggestion or feedback, comment below. Thanks for reading.

    Author SupunKavinda